-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix line folding #87
Fix line folding #87
Conversation
@jonasfj This is the draft. Forgive the commits from #84, I will rebase this branch once it is merged. Also, I added a funky method to reduce the code duplication after several iterations for both Will add tests for this then. |
lib/src/strings.dart
Outdated
/// Assumes the user did not try to account for windows documents by using | ||
/// `\r\n` already |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have to state that. The user should never account for encoding details.
Sematically, a YAML document consists of YamlNodes, see package:yaml
If a YamlNode is a YamlScalar then it can have a YamlScalar.value
that is a string.
If value
is a string, then that is the string that must be returned when the document is parsed.
If the user actually gives us a string that contains \r\n
we can't encode it in folded or literal mode. The only way to represent a \r
character is to do quoted string "\r\n"
-- am I wrong? Is it possible to string escaping in folded or literal strings? I imagine it's not possible, but I could be wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest you take a look at:
https://github.com/dart-lang/yaml_edit/blob/main/lib/src/editor.dart#L564-L583
Essentially, if I use YamlEditor.update(path, foo)
then deepEquals(YamlEditor.parseAt(path), foo) == true
. And this MUST always be true (assuming that deepEquals
ignores the styling and only looks at .value
).
No matter what string I insert or what style I attempt to use, YamlEditor must fallback to use a less pretty style, if it can't represent a string in the style I chose.
For example, if I try to use special characters or \0
null bytes or whatever, then this should still hold. But YamlEditor might have to fallback to quoted strings, even if I requested a Folded string style. The style is just a hint, not a requirement, because not all styles can be used for all values.
lib/src/strings.dart
Outdated
/// [_tryYamlEncodeFolded] when in [CollectionStyle.BLOCK]. | ||
( | ||
String literalPrefix, | ||
String indent, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning this seems a bit extreme. The caller already has indentSize
, why can't the caller just do ' ' * indentSize
, I think should be fine.
In general, I'd suggest we only pass around indentation as an integer.
lib/src/strings.dart
Outdated
String trimmedString, | ||
String strippedString, | ||
) _prepareBlockString( | ||
String blockIndicator, String string, int indentSize, String lineEnding) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw, feel free to insert assertions, they are practically free when a program is compiled in release mode. And a few sanity checks like this can only help catch bugs :D
String blockIndicator, String string, int indentSize, String lineEnding) { | |
String blockIndicator, String string, int indentSize, String lineEnding) { | |
assert(indentSize >= 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realised there is no need to assert
. We can pass in the indent
and just apply to all strings that appear after \n
lib/src/strings.dart
Outdated
|
||
final trimmedString = string.trimRight(); | ||
final removedPortion = string.substring(trimmedString.length); | ||
String _tryYamlEncodeFolded(String string, int indentSize, String lineEnding) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be bigger than what you set out to do. But reading the code, I thinking that maybe we should restructure it a bit.
Maybe we should change how _tryYamlEncodeFolded
and _tryYamlEncodeLiteral
works.
What if we change the signature to:
/// Try to encode [value] as a _YAML folded string_ indent with [indent] spaces.
///
/// This method will apply an appropriate _chomping indicator_, if this is necessary
/// in order to correctly encode [value].
///
/// **Example** of a folded string.
/// ```yaml
/// key: >-
/// my folded
/// string
/// ```
///
/// See: https://yaml.org/spec/1.2.2/#813-folded-style
///
/// Returns `null`, if [value] cannot be encoded as a YAML folded string.
String? _tryYamlEncodeFolded(String value, int indent, String lineEnding)
So we return null
, when the value can't be encoded correctly as a folded string.
Then we get to change yamlEncodeBlockScalar
such that it tries to encode in the style hinted by YamlScalar.style
, and if this fails, we can fallback to a different block-mode, and if all block-modes fail we can fallback to quoted string mode.
This way, we don't have to write yamlEncodeBlockScalar
such that it figures out of the value can be encoded in folded mode, and then _tryYamlEncodeFolded
does the encoding.
Which is also weird, because _tryYamlEncodeFolded
starts with try
, but it actually doesn't have a mechanism for communicating back to say "oh, I can't correctly encode this value".
This way: _tryYamlEncodeFolded
is responsible for:
- (A) Determining if
value
can be encoded as a folded string. - (B) Actually encoding
value
as a folded string.
And yamlEncodeBlockScalar
is responsible for:
- (1) Trying
_tryYamlEncodeFolded
or_tryYamlEncodeLiteral
(depending onYamlScalar.style
). - (2) Falling back to another style if the preferred style couldn't be used.
- (3) Falling back to using quoted string encoding (which always works), if none of the block styles could be used.
And yamlEncodeBlockScalar
doesn't have to know what values can be encoded in what styles, it just determines the preferred option and tries it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't see this. Lemme get to it. Scratch the request to review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No rush :)
We can also do it in a follow up.
Btw, there values we can't encode right?
Like special characters and some weird combination of whitespace and line breaks I would also assume.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If so, this PR is good to go.
No rush :)
We can also do it in a follow up.
Btw, there values we can't encode right? Like special characters and some weird combination of white space and line breaks I would also assume.
Your suggestions in the previous comment are in the ball park of what I had in mind.
I realised the code below makes the tests pass and we never get to encode the folded string as >+ my string
as it trims the space as the end including \n
. I removed #44 for that reason.
No rush :)
We can also do it in a follow up.
Btw, there values we can't encode right? Like special characters and some weird combination of white space and line breaks I would also assume.
Yeah. The trailing line breaks and white-space stand out. Your suggestions actually make it possible to correctly debug what the issue is. I'll look into it.
I can still tackle it in this PR. No issue. Your call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your suggestions actually make it possible to correctly debug what the issue is. I'll look into it.
I can still tackle it in this PR. No issue. Your call.
It's probably a good idea to spend some time making sure we get this right :D
It'd be really nice if we didn't have so many weird string encoding bugs.
8abc4e7
to
009c714
Compare
lib/src/strings.dart
Outdated
String literalPrefix, | ||
String trimmedString, | ||
String strippedString | ||
) _prepareBlockString( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure this method is worth the dedupliation?
It seems weird that we do trimmed.replaceAll('\n', lineEnding + indent)
and then inside _tryYamlEncodeFolded
we split it on lineEnding
.
It's unclear what we return from this method too.
I think may we should just let this be duplicated into the two method.
Then _tryYamlEncodeFolded
can split on \n
instead of splitting on lineEnding
, which is probably going to result in the same thing, but hopefully going to be less confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was part of the existing code. I just extracted that function because encoding with ScalarStyle.FOLDED
or ScalarStyle.LITERAL
follow the same initial steps.
It seems weird that we do
trimmed.replaceAll('\n', lineEnding + indent)
and then inside_tryYamlEncodeFolded
we split it onlineEnding
.It's unclear what we return from this method too.
I think may we should just let this be duplicated into the two method.
Then
_tryYamlEncodeFolded
can split on\n
instead of splitting onlineEnding
, which is probably going to result in the same thing, but hopefully going to be less confusing.
I am getting confused. Help me clarify something. If I do,
final multiLine = '''
My multiline string
is great
''';
Will Dart
return the line breaks
as \r\n
when on Windows or just plain \n
. This influenced my decision when refactoring that piece of code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually don't know, I would suspect it always uses \n
, because anything just seems crazy 🤣
But as far as yaml_edit is concerned I don't see how that's relevant.
If package:yaml
parses a YAML document that uses \n\r
as line breaks, it won't included that kind of line breaks in folded or literal strings.
The only way to get that into a string in YAML is to use a double quoted string with escape characters: "\n\r"
.
I'm like 99% sure that parsing YAML on Windows won't cause multi-line strings to be broken by \n\r
, they'll always be broken by \n
once parsed. The actual YAML file on disk should contain \n\r
, that's correct. But when parsed the YamlScalar.value
should never be a string that contains \n\r
, unless it was a double quoted string with an escape sequence.
Does that makes sense? Yes it's easy to get lost, and I'll admit there is a fair 12.3% risk I missed something here 🤣
lib/src/strings.dart
Outdated
trimmedString = | ||
trimmedSplit.foldIndexed(first, (index, previous, current) { | ||
if (index == 0) return previous; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take a look at reduceIndexed
, in which case you might not need the index and you can just use reduce
.
Calls combine for each element except the first.
Then you probably won't need the if (trimmedSplit.length > 1) {... else ....
either.
I suspect that [foo].reduce(...) == foo
(a list of length one always reduces to the first element).
lib/src/strings.dart
Outdated
|
||
final trimmedString = string.trimRight(); | ||
final removedPortion = string.substring(trimmedString.length); | ||
String _tryYamlEncodeFolded(String string, int indentSize, String lineEnding) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your suggestions actually make it possible to correctly debug what the issue is. I'll look into it.
I can still tackle it in this PR. No issue. Your call.
It's probably a good idea to spend some time making sure we get this right :D
It'd be really nice if we didn't have so many weird string encoding bugs.
- Fix error thrown when inserting in nested list using `spliceList` method | ||
([#83](https://github.com/dart-lang/yaml_edit/issues/83)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to file this as a separate PR, if you want. Then we get to land it quickly :D
Tagging it along here is harmless though.
lib/src/strings.dart
Outdated
/// | ||
/// Apply the `keep (+)` chomping indicator for trailing whitespace to be | ||
/// treated as content. | ||
if (string.endsWith('\n ') || string.endsWith('\n')) return '+'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (string.endsWith('\n ') || string.endsWith('\n')) return '+';
What if it ends with '\n '
?
I'm asking because I'm not 100% sure 🤣
It just seems like we could use a regular expression, like \n *$
lib/src/strings.dart
Outdated
/// It is important that we ensure that [string] is free of unprintable | ||
/// characters by calling [_hasUnprintableCharacters] before invoking this | ||
/// function. | ||
String _tryYamlEncodeSingleQuoted(String string) { | ||
String? _tryYamlEncodeSingleQuoted(String string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we update the documentation comment, to say that it return null
, if [string]
can't be encoded as a single quoted string?
Also I guess we can remove the part from the documentation string about _hasUnprintableCharacters
And instead, just check if string contains anything from _hasUnprintableCharacters
, and if so, just return null
. Then the documentation comment becomes much simpler.
Like:
/// Encodes [string] as YAML single quoted string.
///
/// Returns `null`, if [string] can't be encoded as single-quoted string.
/// This might happen if it contains line-breaks or [_hasUnprintableCharacters].
///
/// See: https://yaml.org/spec/1.2.2/#732-single-quoted-style
lib/src/strings.dart
Outdated
if (!_hasUnprintableCharacters(val)) { | ||
if (value.style == ScalarStyle.SINGLE_QUOTED) { | ||
encoded = _tryYamlEncodeSingleQuoted(val); | ||
} else if (value.style == ScalarStyle.FOLDED) { | ||
encoded = _tryYamlEncodeFolded(val, indentation, lineEnding); | ||
} else if (value.style == ScalarStyle.LITERAL) { | ||
encoded = _tryYamlEncodeLiteral(val, indentation, lineEnding); | ||
} | ||
} | ||
|
||
if (encoded != null) return encoded; | ||
} | ||
|
||
return _tryYamlEncodePlain(value.value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about something like this?
We make all the _try...
methods return null
if they can't encode correctly.
And they we try the best option first, and just try a few relevant fallbacks.
It's probably rare that literal works when folded doesn't, and vice versa, but it can happen, right? And trying is probably rather cheap.
I realize that we do end up checking _hasUnprintableCharacters
inside each method. But I'm guess it's fairly cheap, and we'll only run it more than once in the cases where there actually is something unprintable. So it won't really affect performance much (after all, it's rare that there are unprintable characters).
if (!_hasUnprintableCharacters(val)) { | |
if (value.style == ScalarStyle.SINGLE_QUOTED) { | |
encoded = _tryYamlEncodeSingleQuoted(val); | |
} else if (value.style == ScalarStyle.FOLDED) { | |
encoded = _tryYamlEncodeFolded(val, indentation, lineEnding); | |
} else if (value.style == ScalarStyle.LITERAL) { | |
encoded = _tryYamlEncodeLiteral(val, indentation, lineEnding); | |
} | |
} | |
if (encoded != null) return encoded; | |
} | |
return _tryYamlEncodePlain(value.value); | |
switch (value.style) { | |
// Prefer 'plain', fallback to "double quoted" | |
case ScalarStyle.PLAIN: | |
return _tryYamlEncodePlain(value.value) ?? _yamlEncodeDoubleQuoted(val); | |
// Prefer 'single quoted', fallback to "double quoted" | |
case ScalarStyle.SINGLE_QUOTED: | |
return _tryYamlEncodeSingleQuoted(val) ?? _yamlEncodeDoubleQuoted(val); | |
// Prefer folded string, try literal as fallback | |
// otherwise fallback to "double quoted" | |
case ScalarStyle.FOLDED: | |
return _tryYamlEncodeFolded(val, indentation, lineEnding) | |
?? _tryYamlEncodeLiteral(val, indentation, lineEnding) | |
?? _yamlEncodeDoubleQuoted(val); | |
// Prefer literal string, try folded as fallback | |
// otherwise fallback to "double quoted" | |
case ScalarStyle.LITERAL: | |
return _tryYamlEncodeLiteral(val, indentation, lineEnding) | |
?? _tryYamlEncodeFolded(val, indentation, lineEnding) | |
?? _yamlEncodeDoubleQuoted(val); | |
// Prefer plain, try 'single quote', fallback to "double quoted" | |
default: | |
return _tryYamlEncodePlain(value.value) | |
?? _tryYamlEncodeSingleQuoted(val) | |
?? _yamlEncodeDoubleQuoted(val); | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_tryYamlEncodePlain
internally falls back to _yamlEncodeDoubleQuoted
. Should I make it return null
if it fails instead? I thought of changing the name but left it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, let's, then it'll be consistent.
If we do a switch statement like above, we get to deal with one thing in each method. And the heuristics of what we fall back to is all located in the switch statement.
lib/src/strings.dart
Outdated
final isString = value is String; | ||
|
||
return _tryYamlEncodePlain(value.value); | ||
if (isString && _hasUnprintableCharacters(value)) { | ||
return _yamlEncodeDoubleQuoted(value); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If value is not a string, then we can always do a plain encoding, right?
if (value is! string) {
return value.toString();
}
or since we know that a plain encoding will work we can do:
if (value is! string) {
return _tryYamlEncodePlain(value)!;
}
Or we could make a method called yamlEncodePlain(value)
which will do a plain encoding, and throw an AssertionError
if that's not possible.
lib/src/strings.dart
Outdated
/// Prefer folded string, try literal as fallback | ||
/// otherwise fallback to "double quoted" | ||
case ScalarStyle.FOLDED when isString: | ||
return _tryYamlEncodeFolded(value, indentation, lineEnding) ?? | ||
_yamlEncodeDoubleQuoted(value); | ||
|
||
/// Prefer literal string, try folded as fallback | ||
/// otherwise fallback to "double quoted" | ||
case ScalarStyle.LITERAL when isString: | ||
return _tryYamlEncodeLiteral(value, indentation, lineEnding) ?? | ||
_yamlEncodeDoubleQuoted(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the comments.
/// Prefer folded string, try literal as fallback | |
/// otherwise fallback to "double quoted" | |
case ScalarStyle.FOLDED when isString: | |
return _tryYamlEncodeFolded(value, indentation, lineEnding) ?? | |
_yamlEncodeDoubleQuoted(value); | |
/// Prefer literal string, try folded as fallback | |
/// otherwise fallback to "double quoted" | |
case ScalarStyle.LITERAL when isString: | |
return _tryYamlEncodeLiteral(value, indentation, lineEnding) ?? | |
_yamlEncodeDoubleQuoted(value); | |
/// Prefer folded string, fallback to "double quoted" | |
case ScalarStyle.FOLDED when isString: | |
return _tryYamlEncodeFolded(value, indentation, lineEnding) ?? | |
_yamlEncodeDoubleQuoted(value); | |
/// Prefer literal string, fallback to "double quoted" | |
case ScalarStyle.LITERAL when isString: | |
return _tryYamlEncodeLiteral(value, indentation, lineEnding) ?? | |
_yamlEncodeDoubleQuoted(value); |
or try with two fallbacks, though I can see that this might be overkill 🤣
/// Prefer folded string, try literal as fallback | |
/// otherwise fallback to "double quoted" | |
case ScalarStyle.FOLDED when isString: | |
return _tryYamlEncodeFolded(value, indentation, lineEnding) ?? | |
_yamlEncodeDoubleQuoted(value); | |
/// Prefer literal string, try folded as fallback | |
/// otherwise fallback to "double quoted" | |
case ScalarStyle.LITERAL when isString: | |
return _tryYamlEncodeLiteral(value, indentation, lineEnding) ?? | |
_yamlEncodeDoubleQuoted(value); | |
/// Prefer folded string, try literal as fallback | |
/// otherwise fallback to "double quoted" | |
case ScalarStyle.FOLDED when isString: | |
return _tryYamlEncodeFolded(value, indentation, lineEnding) ?? | |
_tryYamlEncodeLiteral(value, indentation, lineEnding) ?? | |
_yamlEncodeDoubleQuoted(value); | |
/// Prefer literal string, try folded as fallback | |
/// otherwise fallback to "double quoted" | |
case ScalarStyle.LITERAL when isString: | |
return _tryYamlEncodeLiteral(value, indentation, lineEnding) ?? | |
_tryYamlEncodeFolded(value, indentation, lineEnding) ?? | |
_yamlEncodeDoubleQuoted(value); |
lib/src/strings.dart
Outdated
/// Checks if a [string] has any unprintable characters or characters that | ||
/// should be explicitly wrapped in double quotes according to | ||
/// [unprintableCharCodes] and [doubleQuoteEscapeChars] respectively. | ||
/// | ||
/// It should be noted that this check excludes the `\n` (line break) | ||
/// character as it is encoded correctly when using [ScalarStyle.LITERAL] or | ||
/// [ScalarStyle.FOLDED]. | ||
bool _shouldDoubleQuote(String string) { | ||
if (string.isEmpty || string.trimLeft().length != string.length) return true; | ||
|
||
final codeUnits = string.codeUnits; | ||
|
||
return doubleQuoteEscapeChars.keys | ||
.whereNot((charUnit) => charUnit == 10) // Anything but line breaks | ||
.any(codeUnits.contains); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we remove this method?
It's too broad.
Example of things it does wrong:
- empty string can be encoded with single quoted strings.
"
,/
and\
can all be encoded in single quoted mode, folded and literal mode.- Can
\t
be encoded in literal mode? maybe? perhaps that's not wise to do?
How about inside each:
_tryYamlEncodeSingleQuoted
_tryYamlEncodeFolded
_tryYamlEncodeLiteral
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I proposed a bit of refactoring in #88
@jonasfj I cherry-picked your changes and fixed some of the issues. I've managed to implement a naive string wrapping for I resolved to wrap the individual lines as I am folding the entire string. Any suggestions for improvement are welcome, |
test/utils_test.dart
Outdated
); | ||
|
||
expect(doc.toString(), equals('|+\n test\n test\n ')); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I tried something like this in an online editor that converted yaml to JSON and I kept getting the resulting string as: "test\ntest\n \n"
Probably because the editor always ended the YAML document with a line-break: |+\n test\n test\n
would become: |+\n test\n test\n \n
(because files normally end with a line break).
I tried to recreate it, and it wasn't a problem. But I'm guessing it's unwise for us to produce a YAML document where adding empty lines to the YAML document causes it to change its semantics.
If I parse the YAML document |+\n test\n test\n \n
, then I won't get "test\ntest\n \n"
.
Example:
- YAML:
A: |+\n test\n test\n
- YAML to JSON:
{"A":"test\ntest\n "}
(this is correct!)
- YAML to JSON:
- If I add
\n
at the end of the YAML I get:A: |+\n test\n test\n \n
- YAML to JSON:
{"A":"test\ntest\n \n"}
(this is wrong)
- YAML to JSON:
- If I add
\nB: true
at the end of the YAML I get:A: |+\n test\n test\n \nB: true
- YAML to JSON:
{"A":"test\ntest\n \n","B":true}
(this is wrong)
So if we allow this as a literal string, we are VERY likely to cause all sorts of breakages next time we try to append a key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did another variant of this test case:
test('generates literal string with trailing line break and space (2)',
() {
final doc = YamlEditor('key1: true');
doc.update(
['key2'],
wrapAsYamlNode('test\ntest\n ', scalarStyle: ScalarStyle.LITERAL),
);
});
it causes a crash:
Assertion failed: (package:yaml_edit) Modification did not result in expected result.
# YAML before edit:
> key1: true
# YAML after edit:
> key1: true
> key2: |+
> test
> test
>
>
I'm beginning to see how a literal string ending in \n
might have to be encoded as a double quoted string. 🤣
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we consider falling back to double quoted mode for literal strings ending in newline + whitespace?
The whole adding a new key afterwards seems like a reasonable thing, and it's a little sad that this can't work.
Of course, we can't know what document the user gives us. So we could also view this as an orthogonal bug to do with adding keys to block maps and entries to block lists, because those are the scenarios where this could go wrong.
I don't know if there is a reasonably way to fix adding keys to block maps that have a preceeding key-value pair where the value is a literal string ending in \n
🤣 -- definitely we should not try to fix that in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also isn't it likely that the chomping indicator +
is always going to cause us this kind of problems? Just asking, I'm not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonasfj The final \n
just before the next key is the issue.
It seems chomping indicators only control the final line break before going to the next YamlScalar
(value in list or key in map) and not content within the scalar itself (which is only controlled by either >
or |
)
After some investigation, we can exclusively use the strip chomping indicator -
for both ScalarStyle.FOLDED
and ScalarStyle.LITERAL
. It always indicates we don't want the last line break that's just before the next key in map or value in the list.
😅 The fixes I've tried coming up with for the keep chomping indicator +
bug were all shots in the dark and just another bug creeping in.
I'm sorry and thanks! The bug was staring right at us from the beginning. This is the line I should have removed before fixing _tryYamlEncodeFolded
yaml_edit/lib/src/strings.dart
Lines 89 to 93 in f3feb50
if (removedPortion.contains('\n')) { | |
result = '>+\n' + ' ' * indentation; | |
} else { | |
result = '>-\n' + ' ' * indentation; | |
} |
Wow, this starting to be a lot, I tried diving in a little bit. My quick takeaway after seeing a few more bugs is that maybe we should be more conservative about what kind of strings we attempt to encode as literal strings. Perhaps also as folded strings. Perhaps it's better to fallback to double quote string in the edge cases, rather than too complicated code. Certainly, we can always try to improve and not make it fallback in a future PR. |
aff90d4
to
14e97ab
Compare
> Fix invalid encoding for string with trailing line breaks or white-space
> Update `_tryYamlEncodedPlain` to return null if string cannot be encoded > Make `_yamlEncodeFlowScalar` and `yamlEncodeBlockScalar` code concise
> `_yamlEncodeFlowScalar` and `_yamlEncodeBlockScalar` always encode YamlScalars
14e97ab
to
ff4b807
Compare
@jonasfj I'm sorry for the I've reduced the scope of this PR and fixed the chomping indicator issue (or rather removed the chomping indicator |
ff4b807
to
a9b5f6a
Compare
} | ||
/// Simplest block style. | ||
/// * https://yaml.org/spec/1.2.2/#812-literal-style | ||
return '|-\n$indent${string.replaceAll('\n', lineEnding + indent)}'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is better than what we had, so let's accept it.
But I suspect this could cause issues for strings that end in empty lines or whitespace, or combination thereof.
I think we should land this as is, it's an overall improvement. I think we should try to add more tests that challenge I think we should try to just make smaller PRs that only touch either |
… source_span, stream_channel, term_glyph, tools, vector_math, watcher, webdev, yaml_edit Revisions updated by `dart tools/rev_sdk_deps.dart`. dartdoc (https://github.com/dart-lang/dartdoc/compare/88df88c..7e5da60): 7e5da609 2024-06-26 Sam Rawlins Simplify some end2end tests into smaller tests (dart-lang/dartdoc#3795) 36f1fc74 2024-06-26 Sam Rawlins Store comment reference data on ModelNode (dart-lang/dartdoc#3797) 7ed6ef80 2024-06-24 Sam Rawlins Hide constructors that cannot be called or referenced by user code (dart-lang/dartdoc#3796) ecosystem (https://github.com/dart-lang/ecosystem/compare/31148cd..54ca01a): 54ca01a 2024-06-25 Devon Carew improvements for transferred issues (dart-lang/ecosystem#274) html (https://github.com/dart-lang/html/compare/3bc803d..f6c2c71): f6c2c71 2024-06-24 Kevin Moore update lints (dart-archive/html#249) http (https://github.com/dart-lang/http/compare/4d8e7ef..bf96551): bf96551 2024-06-26 Nate Bosch Call out more limitations of runWithClient (dart-lang/http#1248) eb189e1 2024-06-26 Brian Quinlan [cupertino_http] Fix a bug where content-length was not set for multipart messages (dart-lang/http#1247) 0bbd166 2024-06-25 Hossein Yousefi [cronet_http] Upgrade jni and jnigen to 0.9.3 (dart-lang/http#1246) dce17c6 2024-06-24 Kevin Moore misc: add missing packages to root readme (dart-lang/http#1245) 0b40397 2024-06-24 Kevin Moore update lints (dart-lang/http#1244) 2971f32 2024-06-24 Kevin Moore Update lints (dart-lang/http#1243) 497bc15 2024-06-24 Parker Lougheed Update various flutter.dev and dart.dev links (dart-lang/http#1238) 5f6fcae 2024-06-24 Parker Lougheed Fix some minor spelling and grammar mistakes (dart-lang/http#1239) b7ec613 2024-06-24 Anikate De pkgs/ok_http: DevTools Networking Support. (dart-lang/http#1242) json_rpc_2 (https://github.com/dart-lang/json_rpc_2/compare/5b1cbd6..616937f): 616937f 2024-06-26 Kevin Moore Update lints, test JS & Wasm (dart-archive/json_rpc_2#116) mime (https://github.com/dart-lang/mime/compare/4ca2f5e..fd7010b): fd7010b 2024-06-24 Kevin Moore bump lints (dart-archive/mime#127) 99e9855 2024-06-24 Jonas Finnemann Jensen Add `topics` to `pubspec.yaml` (dart-archive/mime#121) mockito (https://github.com/dart-lang/mockito/compare/2302814..9deddcf): 9deddcf 2024-06-25 Sam Rawlins Fix lint in examples. e99ba54 2024-06-24 Sam Rawlins Properly generate code for parameter default value Strings. source_span (https://github.com/dart-lang/source_span/compare/59a3903..89520f3): 89520f3 2024-06-24 Kevin Moore bump lints (dart-lang/source_span#114) stream_channel (https://github.com/dart-lang/stream_channel/compare/b41ff7a..dc620d2): dc620d2 2024-06-24 Kevin Moore bump lints (dart-lang/stream_channel#108) term_glyph (https://github.com/dart-lang/term_glyph/compare/c86e817..6c2a977): 6c2a977 2024-06-24 Kevin Moore bump lints (dart-lang/term_glyph#54) tools (https://github.com/dart-lang/tools/compare/e660a68..4c60686): 4c60686 2024-06-24 Christopher Fujino delete old log file if it exceeds kMaxLogFileSize of 25MiB (dart-lang/tools#277) 7a231e5 2024-06-24 Parker Lougheed [unified_analytics] Add lints to consistently use final (dart-lang/tools#278) vector_math (https://github.com/google/vector_math.dart/compare/e7b11a2..a4304d1): a4304d1 2024-06-24 Kevin Moore Update and fix lints, require Dart 3.1 (google/vector_math.dart#328) 253f69b 2024-06-24 Kevin Moore blast_repo fixes (google/vector_math.dart#327) watcher (https://github.com/dart-lang/watcher/compare/c00fc2a..f312f1f): f312f1f 2024-06-24 Kevin Moore update lints (dart-lang/watcher#169) webdev (https://github.com/dart-lang/webdev/compare/c566112..75417c0): 75417c09 2024-06-26 Ben Konyi Remove remaining MV2 logic (dart-lang/webdev#2453) yaml_edit (https://github.com/dart-lang/yaml_edit/compare/08a146e..ad3292c): ad3292c 2024-06-27 Kavisi Fix line folding (dart-lang/yaml_edit#87) Change-Id: Ie2f914dcf97e10cc5a8501c926ddd7e254a0d151 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373443 Auto-Submit: Devon Carew <devoncarew@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
* Fix line folding to match specification * Add test case and remove TODO for fix * Remove indent creation from utility function * Update changelog * Return null if strings cannot be encoded in desired `ScalarStyle` > Fix invalid encoding for string with trailing line breaks or white-space * Clean up code > Update `_tryYamlEncodedPlain` to return null if string cannot be encoded > Make `_yamlEncodeFlowScalar` and `yamlEncodeBlockScalar` code concise * Remove unnecessary assertions and add docs > `_yamlEncodeFlowScalar` and `_yamlEncodeBlockScalar` always encode YamlScalars * Minor refactoring * Fix condition check for encoding as literal/folded * Remove keep chomping indicator references * Update lib/src/strings.dart --------- Co-authored-by: Jonas Finnemann Jensen <jonasfj@google.com> Co-authored-by: Jonas Finnemann Jensen <jopsen@gmail.com>
This PR:
ScalarStyle.FOLDED
#41>+
#44ScalarStyle.LITERAL
#86Contribution guidelines:
dart format
.Note that many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.