diff --git a/pkgs/yaml_edit/lib/src/equality.dart b/pkgs/yaml_edit/lib/src/equality.dart index 0c6a9526a..cf6404481 100644 --- a/pkgs/yaml_edit/lib/src/equality.dart +++ b/pkgs/yaml_edit/lib/src/equality.dart @@ -91,6 +91,20 @@ YamlNode getKeyNode(YamlMap map, Object? key) { return map.nodes.keys.firstWhere((node) => deepEquals(node, key)) as YamlNode; } +/// Returns the entry associated with a [mapKey] and its index in the [map]. +({int index, YamlNode keyNode, YamlNode valueNode}) getYamlMapEntry( + YamlMap map, + Object? mapKey, +) { + for (final (index, MapEntry(:key, :value)) in map.nodes.entries.indexed) { + if (deepEquals(key, mapKey)) { + return (index: index, keyNode: key, valueNode: value); + } + } + + throw YamlException('$mapKey not found in map', map.span); +} + /// Returns the [YamlNode] after the [YamlNode] corresponding to the provided /// [key]. YamlNode? getNextKeyNode(YamlMap map, Object? key) { diff --git a/pkgs/yaml_edit/lib/src/list_mutations.dart b/pkgs/yaml_edit/lib/src/list_mutations.dart index 17da6dd77..7613da65c 100644 --- a/pkgs/yaml_edit/lib/src/list_mutations.dart +++ b/pkgs/yaml_edit/lib/src/list_mutations.dart @@ -306,72 +306,48 @@ SourceEdit _insertInFlowList( /// [index] should be non-negative and less than or equal to `list.length`. SourceEdit _removeFromBlockList( YamlEditor yamlEdit, YamlList list, YamlNode nodeToRemove, int index) { - RangeError.checkValueInInterval(index, 0, list.length - 1); - - var end = getContentSensitiveEnd(nodeToRemove); - - /// If we are removing the last element in a block list, convert it into a - /// flow empty list. - if (list.length == 1) { - final start = list.span.start.offset; - - return SourceEdit(start, end - start, '[]'); - } + final listSize = list.length; + RangeError.checkValueInInterval(index, 0, listSize - 1); final yaml = yamlEdit.toString(); final span = nodeToRemove.span; - /// Adjust the end to clear the new line after the end too. - /// - /// We do this because we suspect that our users will want the inline - /// comments to disappear too. - final nextNewLine = yaml.indexOf('\n', end); - if (nextNewLine != -1) { - end = nextNewLine + 1; - } - - /// If the value is empty - if (span.length == 0) { - var start = span.start.offset; - return SourceEdit(start, end - start, ''); - } - - /// -1 accounts for the fact that the content can start with a dash - var start = yaml.lastIndexOf('-', span.start.offset - 1); - - /// Check if there is a `-` before the node - if (start > 0) { - final lastHyphen = yaml.lastIndexOf('-', start - 1); - final lastNewLine = yaml.lastIndexOf('\n', start - 1); - if (lastHyphen > lastNewLine) { - start = lastHyphen + 2; - - /// If there is a `-` before the node, we need to check if we have - /// to update the indentation of the next node. - if (index < list.length - 1) { - /// Since [end] is currently set to the next new line after the current - /// node, check if we see a possible comment first, or a hyphen first. - /// Note that no actual content can appear here. - /// - /// We check this way because the start of a span in a block list is - /// the start of its value, and checking from the back leaves us - /// easily confused if there are comments that have dashes in them. - final nextHash = yaml.indexOf('#', end); - final nextHyphen = yaml.indexOf('-', end); - final nextNewLine = yaml.indexOf('\n', end); - - /// If [end] is on the same line as the hyphen of the next node - if ((nextHash == -1 || nextHyphen < nextHash) && - nextHyphen < nextNewLine) { - end = nextHyphen; - } - } - } else if (lastNewLine > lastHyphen) { - start = lastNewLine + 1; - } - } - - return SourceEdit(start, end - start, ''); + final isEmptySpan = span.length == 0; // Just the '-' + final end = getContentSensitiveEnd(nodeToRemove); + + return removeBlockCollectionEntry( + yaml, + blockCollection: list, + collectionIndent: getListIndentation(yaml, list), + isFirstEntry: index == 0, + isSingleEntry: listSize == 1, + isLastEntry: index >= listSize - 1, + nodeToRemoveOffset: ( + start: yaml.lastIndexOf( + '-', + isEmptySpan ? span.start.offset : span.start.offset - 1, + ), + end: isEmptySpan ? end + 1 : end, + ), + lineEnding: getLineEnding(yaml), + nextBlockNodeInfo: () { + final nextNode = list.nodes[index + 1]; + final nextNodeSpan = nextNode.span; + final offset = nextNodeSpan.start.offset; + + final hyphenOffset = yaml.lastIndexOf( + '-', + nextNodeSpan.length == 0 ? offset : offset - 1, + ); + + final nearestLineEnding = yaml.lastIndexOf('\n', hyphenOffset); + + return ( + nearestLineEnding: nearestLineEnding, + nextNodeColStart: hyphenOffset - (nearestLineEnding + 1), + ); + }, + ); } /// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to diff --git a/pkgs/yaml_edit/lib/src/map_mutations.dart b/pkgs/yaml_edit/lib/src/map_mutations.dart index 46e8c7935..04c27d0ee 100644 --- a/pkgs/yaml_edit/lib/src/map_mutations.dart +++ b/pkgs/yaml_edit/lib/src/map_mutations.dart @@ -36,13 +36,11 @@ SourceEdit updateInMap( /// removing the element at [key] when re-parsed. SourceEdit removeInMap(YamlEditor yamlEdit, YamlMap map, Object? key) { assert(containsKey(map, key)); - final keyNode = getKeyNode(map, key); - final valueNode = map.nodes[keyNode]!; if (map.style == CollectionStyle.FLOW) { - return _removeFromFlowMap(yamlEdit, map, keyNode, valueNode); + return _removeFromFlowMap(yamlEdit, map, key); } else { - return _removeFromBlockMap(yamlEdit, map, keyNode, valueNode); + return _removeFromBlockMap(yamlEdit, map, key); } } @@ -169,74 +167,49 @@ SourceEdit _replaceInFlowMap( } /// Performs the string operation on [yamlEdit] to achieve the effect of -/// removing the [keyNode] from the map, bearing in mind that this is a block +/// removing the [key] from the map, bearing in mind that this is a block /// map. -SourceEdit _removeFromBlockMap( - YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode valueNode) { - final keySpan = keyNode.span; - var end = getContentSensitiveEnd(valueNode); +SourceEdit _removeFromBlockMap(YamlEditor yamlEdit, YamlMap map, Object? key) { + final (index: entryIndex, :keyNode, :valueNode) = getYamlMapEntry(map, key); final yaml = yamlEdit.toString(); - final lineEnding = getLineEnding(yaml); - - if (map.length == 1) { - final start = map.span.start.offset; - final nextNewLine = yaml.indexOf(lineEnding, end); - if (nextNewLine != -1) { - // Remove everything up to the next newline, this strips comments that - // follows on the same line as the value we're removing. - // It also ensures we consume colon when [valueNode.value] is `null` - // because there is no value (e.g. `key: \n`). Because [valueNode.span] in - // such cases point to the colon `:`. - end = nextNewLine; - } else { - // Remove everything until the end of the document, if there is no newline - end = yaml.length; - } - return SourceEdit(start, end - start, '{}'); - } - - var start = keySpan.start.offset; - - /// Adjust the end to clear the new line after the end too. - /// - /// We do this because we suspect that our users will want the inline - /// comments to disappear too. - final nextNewLine = yaml.indexOf(lineEnding, end); - if (nextNewLine != -1) { - end = nextNewLine + lineEnding.length; - } else { - // Remove everything until the end of the document, if there is no newline - end = yaml.length; - } - - final nextNode = getNextKeyNode(map, keyNode); - - if (start > 0) { - final lastHyphen = yaml.lastIndexOf('-', start - 1); - final lastNewLine = yaml.lastIndexOf(lineEnding, start - 1); - if (lastHyphen > lastNewLine) { - start = lastHyphen + 2; - - /// If there is a `-` before the node, and the end is on the same line - /// as the next node, we need to add the necessary offset to the end to - /// make sure the next node has the correct indentation. - if (nextNode != null && - nextNode.span.start.offset - end <= nextNode.span.start.column) { - end += nextNode.span.start.column; - } - } else if (lastNewLine > lastHyphen) { - start = lastNewLine + lineEnding.length; - } - } + final mapSize = map.length; + final keySpan = keyNode.span; - return SourceEdit(start, end - start, ''); + return removeBlockCollectionEntry( + yaml, + blockCollection: map, + collectionIndent: getMapIndentation(yaml, map), + isFirstEntry: entryIndex == 0, + isSingleEntry: mapSize == 1, + isLastEntry: entryIndex >= mapSize - 1, + nodeToRemoveOffset: ( + // A block map only exists because of its first key. + start: entryIndex == 0 ? map.span.start.offset : keySpan.start.offset, + end: valueNode.span.length == 0 + ? keySpan.end.offset + 2 // Null value have no span. Skip ":". + : getContentSensitiveEnd(valueNode), + ), + lineEnding: getLineEnding(yaml), + + // Only called when the next node is present. Never before. + nextBlockNodeInfo: () { + final nextKeyNode = map.nodes.keys.elementAt(entryIndex + 1) as YamlNode; + final nextKeySpan = nextKeyNode.span.start; + + return ( + nearestLineEnding: yaml.lastIndexOf('\n', nextKeySpan.offset), + nextNodeColStart: nextKeySpan.column + ); + }, + ); } /// Performs the string operation on [yamlEdit] to achieve the effect of -/// removing the [keyNode] from the map, bearing in mind that this is a flow +/// removing the [key] from the map, bearing in mind that this is a flow /// map. -SourceEdit _removeFromFlowMap( - YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode valueNode) { +SourceEdit _removeFromFlowMap(YamlEditor yamlEdit, YamlMap map, Object? key) { + final (index: _, :keyNode, :valueNode) = getYamlMapEntry(map, key); + var start = keyNode.span.start.offset; var end = valueNode.span.end.offset; final yaml = yamlEdit.toString(); diff --git a/pkgs/yaml_edit/lib/src/utils.dart b/pkgs/yaml_edit/lib/src/utils.dart index 0e28ac1a8..e446ad92c 100644 --- a/pkgs/yaml_edit/lib/src/utils.dart +++ b/pkgs/yaml_edit/lib/src/utils.dart @@ -2,10 +2,13 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:math'; + import 'package:source_span/source_span.dart'; import 'package:yaml/yaml.dart'; import 'editor.dart'; +import 'source_edit.dart'; import 'wrap.dart'; /// Invoke [fn] while setting [yamlWarningCallback] to [warn], and restore @@ -273,6 +276,304 @@ String getLineEnding(String yaml) { return windowsNewlines > unixNewlines ? '\r\n' : '\n'; } +final _nonSpaceMatch = RegExp(r'[^ \t]'); + +/// Skip empty lines and returns the offset of the last possible line ending +/// only if the [offset] is a valid offset within the [yaml] string that +/// points to first line ending. +/// +/// The [blockIndent] is used to truncate any comments more indented than the +/// parent collection that may affect other block entries within the collection +/// that may have block scalars. +int indexOfLastLineEnding( + String yaml, { + required int offset, + required int blockIndent, +}) { + if (yaml.isEmpty || offset == -1) return yaml.length; + + final lastOffset = yaml.length - 1; + var currentOffset = min(offset, lastOffset); + + // Unsafe. Cannot start our scanner state machine in an unguarded state. + if (yaml[currentOffset] != '\r' && yaml[currentOffset] != '\n') { + return currentOffset; + } + + var lineEndingIndex = currentOffset; + + // Skip empty lines and any comments indented more than the block entry. Such + // comments are hazardous to block scalars. + scanner: + while (currentOffset <= lastOffset) { + switch (yaml[currentOffset]) { + case '\r': + { + // Skip carriage return if possible. No use to us if we have a line + // feed after. + if (currentOffset < lastOffset && yaml[currentOffset + 1] == '\n') { + ++currentOffset; + } + + continue indentChecker; + } + + indentChecker: + case '\n': + { + lineEndingIndex = currentOffset; + ++currentOffset; + + if (currentOffset >= lastOffset) { + lineEndingIndex = lastOffset; + break scanner; + } + + final offsetAfterIndent = yaml.indexOf(RegExp('[^ ]'), currentOffset); + + // No more characters! + if (offsetAfterIndent == -1) { + lineEndingIndex = lastOffset; + break scanner; + } + + final indent = offsetAfterIndent - currentOffset; + currentOffset = offsetAfterIndent; + final charAfterIndent = yaml[currentOffset]; + + if (charAfterIndent case '\r' || '\n') { + continue scanner; + } else if (indent > blockIndent) { + // If more indented than the entry, always attempt to truncate the + // comment or skip it as an empty line. + if (charAfterIndent == '\t') { + continue skipIfEmpty; + } else if (charAfterIndent == '#') { + continue truncateComment; + } + } + + break scanner; + } + + // Guarded by indentChecker. Force tabs to be associated with empty lines + // if seen past the indent. + skipIfEmpty: + case '\t': + { + final nonSpace = yaml.indexOf(_nonSpaceMatch, currentOffset); + + if (nonSpace == -1) { + lineEndingIndex = lastOffset; + } else if (yaml[nonSpace] case '\r' || '\n') { + currentOffset = nonSpace; + continue scanner; + } + + break scanner; + } + + // Guarded by indentChecker. This ensures we only skip comments indented + // more than the entry itself. + truncateComment: + case '#': + { + final lineFeedOffset = yaml.indexOf('\n', currentOffset); + + if (lineFeedOffset == -1) { + lineEndingIndex = lastOffset; + break scanner; + } + + currentOffset = lineFeedOffset; + continue indentChecker; + } + + default: + break scanner; + } + } + + return lineEndingIndex; +} + +/// Backtracks from the [start] offset and looks for the nearest character +/// that is not a separation space (tab/space) that can be used to declare a +/// nested compact block map +/// +/// ```yaml +/// # In a block list +/// - key: value +/// next: value +/// +/// --- +/// # In an explicit key and its value +/// +/// ? key: value +/// next: value +/// : key: value +/// next: value +/// ``` +/// +/// If a line feed `\n` is encountered first then `compactCharOffset` defaults +/// to -1. Otherwise, only returns a non-negative `compactCharOffset` if +/// `?`, `-` or `:` were seen. +({int compactCharOffset, int lineEndingIndex}) indexOfCompactChar( + String yaml, + int start, +) { + /// Look back past the indent/separation space. + final startOffset = max( + 0, + yaml.lastIndexOf(_nonSpaceMatch, max(0, start - 1)), + ); + + return switch (yaml[startOffset]) { + '\r' || '\n' => (compactCharOffset: -1, lineEndingIndex: startOffset), + + /// Block sequences and explicit keys/values can be used to declare block + /// maps/sequences in a compact-inline notation. + /// + /// - a: b + /// c: d + /// + /// - - a + /// - b + /// + /// OR as an explicit key with its explicit value + /// + /// ? a: b + /// c: d + /// : e: f + /// g: h + /// + /// ? - sequence + /// - as key + /// : - sequence + /// - as value + /// + /// See "Example 8.19 Compact Block Mappings" at + /// https://yaml.org/spec/1.2.2/#822-block-mappings + '-' || '?' || ':' => (compactCharOffset: startOffset, lineEndingIndex: -1), + _ => (compactCharOffset: -1, lineEndingIndex: -1) + }; +} + +typedef NextBlockNodeInfo = ({int nearestLineEnding, int nextNodeColStart}); +typedef BlockNodeOffset = ({int start, int end}); + +/// Removes a block entry and its line ending from a [blockCollection] using the +/// [nodeToRemoveOffset] provided. Any trailing comments are also removed. +/// +/// If [blockCollection] is a [YamlMap], the chunk removed corresponds to the +/// key-value pair represented by the offset. If [blockCollection] is a +/// [YamlList], the chunk represents a single element within the list. +/// +/// [nextBlockNodeInfo] is only called if the [blockCollection] has at least +/// 2 entries and the entry being removed is not the last entry in the +/// collection. +SourceEdit removeBlockCollectionEntry( + String yaml, { + required YamlNode blockCollection, + required int collectionIndent, + required bool isFirstEntry, + required bool isSingleEntry, + required bool isLastEntry, + required BlockNodeOffset nodeToRemoveOffset, + required String lineEnding, + required NextBlockNodeInfo Function() nextBlockNodeInfo, +}) { + final isBlockList = blockCollection is YamlList; + + assert( + isBlockList || blockCollection is YamlMap, + 'Expected a block map/list', + ); + + var makeNextNodeCompact = false; + var (:start, :end) = nodeToRemoveOffset; + + // Skip empty lines to the last line break + end = indexOfLastLineEnding( + yaml, + offset: yaml.indexOf('\n', end - 1), + blockIndent: collectionIndent, + ); + + end = min(++end, yaml.length); // Mark it for removal + + if (isSingleEntry) { + // Preserve when sandwiched or if an EOF line ending was present. + if (end < yaml.length || yaml.endsWith('\n')) { + end -= lineEnding == '\r\n' ? 2 : 1; + } + + return SourceEdit(start, end - start, isBlockList ? '[]' : '{}'); + } else if (isLastEntry) { + start = yaml.lastIndexOf('\n', start) + 1; + end = max(yaml.lastIndexOf('\n', blockCollection.span.end.offset) + 1, end); + return SourceEdit(start, end - start, ''); + } + + if (start != 0) { + // Try making it compact in case the collection's parent is a: + // - block sequence + // - explicit key + // - explicit key's explicit value. + if (isFirstEntry) { + final (:compactCharOffset, :lineEndingIndex) = indexOfCompactChar( + yaml, + start, + ); + + if (compactCharOffset != -1) { + start = compactCharOffset + 2; // Skip separation space. + makeNextNodeCompact = true; + } else { + start = lineEndingIndex + 1; + } + } else { + // If not possible, just consume this node's indent. This prevents this + // node from interfering with the next node. + start = yaml.lastIndexOf('\n', start) + 1; + } + } + + final (:nearestLineEnding, :nextNodeColStart) = nextBlockNodeInfo(); + final trueEndOffset = end - 1; + + // Make compact only if we are pointing to same line break from different + // node extremes. This can only be true if we are removing the first + // entry. + // + // ** For block lists ** + // + // [*] Before: + // + // - - value + // - next + // + // [*] After: + // + // - - next + // + // ** For block maps ** + // + // [*] Before: + // + // - key: value + // next: value + // + // [*] After: + // + // - next: value + end = makeNextNodeCompact && nearestLineEnding == trueEndOffset + ? end + nextNodeColStart + : end; + + return SourceEdit(start, end - start, ''); +} + extension YamlNodeExtension on YamlNode { /// Returns the [CollectionStyle] of `this` if `this` is [YamlMap] or /// [YamlList]. diff --git a/pkgs/yaml_edit/test/remove_test.dart b/pkgs/yaml_edit/test/remove_test.dart index 4742b5608..f3adc7c7c 100644 --- a/pkgs/yaml_edit/test/remove_test.dart +++ b/pkgs/yaml_edit/test/remove_test.dart @@ -131,7 +131,6 @@ c: 3 doc.remove([0, 'b']); expect(doc.toString(), equals(''' - a: 1 - c: 3 ''')); }); @@ -184,9 +183,7 @@ b: 2 a: 1 '''); doc.remove(['a']); - expect(doc.toString(), equals(''' -{} -''')); + expect(doc.toString(), equals('{}\n')); }); test('last element should return flow empty map (2)', () { @@ -241,6 +238,29 @@ dev_dependencies: retry:'''); doc.remove(['dev_dependencies', 'retry']); }); + + test('Removes node with preceding block scalar', () { + final doc = YamlEditor(''' +key: >+ + folded with keep chomping + +target-key: value # Comment + # Comment + # Indented, removed + +# Not indented, kept +next: value +'''); + + doc.remove(['target-key']); + expect(doc.toString(), ''' +key: >+ + folded with keep chomping + +# Not indented, kept +next: value +'''); + }); }); group('flow map', () { @@ -312,9 +332,7 @@ dev_dependencies: - 0 '''); doc.remove([0]); - expect(doc.toString(), equals(''' -[] -''')); + expect(doc.toString(), equals('[]\n')); }); test('last element should return flow empty list (2)', () { @@ -564,6 +582,65 @@ b: } ]); }); + + test('Removes comments that may interfere with block scalar', () { + final doc = YamlEditor(''' +- >+ + folded keep chomp + +- - - |+ # Nested + literal keep chomp + + - value # May interfere + # With block node + +- top # With + # Funky + + # Comments + + # Comment +'''); + + doc.remove([1, 1]); + + expect(doc.toString(), ''' +- >+ + folded keep chomp + +- - - |+ # Nested + literal keep chomp + +- top # With + # Funky + + # Comments + + # Comment +'''); + + doc.remove([1]); + + expect(doc.toString(), ''' +- >+ + folded keep chomp + +- top # With + # Funky + + # Comments + + # Comment +'''); + + doc.remove([1]); + + expect(doc.toString(), ''' +- >+ + folded keep chomp + +'''); + }); }); group('flow list', () { diff --git a/pkgs/yaml_edit/test/utils_test.dart b/pkgs/yaml_edit/test/utils_test.dart index 57f0b2a77..7ec1e6bc6 100644 --- a/pkgs/yaml_edit/test/utils_test.dart +++ b/pkgs/yaml_edit/test/utils_test.dart @@ -445,4 +445,183 @@ a: expect(() => assertValidScalar([1]), throwsArgumentError); }); }); + + group('compact char test', () { + test( + 'Returns a valid index of the character used to declare block node' + ' in compact-inline notation for an explicit key', + () { + const yaml = ''' +? - block + - sequence + +? key: value + another: value +'''; + + final mapKeys = (loadYamlNode( + yaml, + ) as YamlMap) + .nodes + .keys + .cast() + .toList(); + + // Compact block lists + expect( + indexOfCompactChar( + yaml, + mapKeys.first.span.start.offset, + ), + equals((compactCharOffset: 0, lineEndingIndex: -1)), + ); + + // Compact block maps + expect( + indexOfCompactChar( + yaml, + mapKeys[1].span.start.offset, + ), + + // Skip first "?" + equals( + (compactCharOffset: yaml.indexOf('?', 1), lineEndingIndex: -1), + ), + ); + }, + ); + + test( + 'Returns a valid index of the character used to declare block node' + ' in compact-inline notation for an explicit value', + () { + /// A key/value is always implicit unless an explicit key is seen. + /// + /// See paragraph after example 8.17 in Block Mappings section. + const yaml = ''' +? key +: - block + - sequence + +? another +: explict: block + value: node +'''; + + final values = (loadYamlNode( + yaml, + ) as YamlMap) + .nodes + .values + .toList(); + + final firstExplicitValueChar = yaml.indexOf(':'); + + // Compact block lists + expect( + indexOfCompactChar(yaml, values.first.span.start.offset), + equals( + (compactCharOffset: firstExplicitValueChar, lineEndingIndex: -1), + ), + ); + + // Compact block maps + expect( + indexOfCompactChar(yaml, values[1].span.start.offset), + + // Skip first ":" + equals( + ( + compactCharOffset: yaml.indexOf(':', firstExplicitValueChar + 1), + lineEndingIndex: -1 + ), + ), + ); + }, + ); + + test( + 'Returns a valid index of the character used to declare block node' + ' in compact-inline notation for a block sequence', + () { + const yaml = ''' +- - block + - sequence + +- key: value + another: value +'''; + + final elements = (loadYamlNode( + yaml, + ) as YamlList) + .nodes; + + // Compact block lists + expect( + indexOfCompactChar(yaml, elements.first.span.start.offset), + equals((compactCharOffset: 0, lineEndingIndex: -1)), + ); + + // Compact block maps + expect( + indexOfCompactChar(yaml, elements[1].span.start.offset), + + // Skip first "?" + equals( + (compactCharOffset: yaml.lastIndexOf('-'), lineEndingIndex: -1), + ), + ); + }, + ); + + test('Returns index of line break when not compact', () { + const yaml = ''' +- + - not compact +- + ? + - key not compact + : + - not compact +'''; + + final list = (loadYamlNode(yaml) as YamlList).nodes; + + // First nested block sequence is not compact + expect( + indexOfCompactChar(yaml, list[0].span.start.offset), + equals((compactCharOffset: -1, lineEndingIndex: yaml.indexOf('\n'))), + ); + + // Explicit key & value not compact. + final map = list[1] as YamlMap; + + final entries = map.nodes; + + expect( + indexOfCompactChar( + yaml, + entries.keys.cast().first.span.start.offset, + ), + equals(( + compactCharOffset: -1, + lineEndingIndex: yaml.indexOf('\n', map.span.start.offset) + )), + ); + + expect( + indexOfCompactChar(yaml, entries.values.first.span.start.offset), + equals( + ( + compactCharOffset: -1, + lineEndingIndex: yaml.indexOf( + '\n', + yaml.indexOf(':', map.span.start.offset), + ) + ), + ), + ); + }); + }); } diff --git a/pkgs/yaml_edit/test/windows_test.dart b/pkgs/yaml_edit/test/windows_test.dart index 50f79e743..16eae4981 100644 --- a/pkgs/yaml_edit/test/windows_test.dart +++ b/pkgs/yaml_edit/test/windows_test.dart @@ -161,9 +161,7 @@ c: 3\r - 0\r '''); doc.remove([0]); - expect(doc.toString(), equals(''' -[]\r -''')); + expect(doc.toString(), equals('[]\r\n')); expectYamlBuilderValue(doc, []); }); @@ -204,9 +202,7 @@ c: 3\r a: 1\r '''); doc.remove(['a']); - expect(doc.toString(), equals(''' -{}\r -''')); + expect(doc.toString(), equals('{}\r\n')); expectYamlBuilderValue(doc, {}); });