• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dart-lang / yaml_edit / 4394861413

pending completion
4394861413

push

github

GitHub
Require Dart 2.19, update to latest lints

28 of 30 new or added lines in 6 files covered. (93.33%)

624 of 654 relevant lines covered (95.41%)

56.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.55
/lib/src/strings.dart
1
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2
// for details. All rights reserved. Use of this source code is governed by a
3
// BSD-style license that can be found in the LICENSE file.
4

5
import 'package:yaml/yaml.dart';
6

7
import 'utils.dart';
8

9
/// Given [value], tries to format it into a plain string recognizable by YAML.
10
/// If it fails, it defaults to returning a double-quoted string.
11
///
12
/// Not all values can be formatted into a plain string. If the string contains
13
/// an escape sequence, it can only be detected when in a double-quoted
14
/// sequence. Plain strings may also be misinterpreted by the YAML parser (e.g.
15
/// ' null').
16
String _tryYamlEncodePlain(Object? value) {
75✔
17
  if (value is YamlNode) {
39✔
18
    AssertionError(
19
        'YamlNodes should not be passed directly into getSafeString!');
20
  }
21

22
  assertValidScalar(value);
75✔
23

24
  if (value is String) {
75✔
25
    /// If it contains a dangerous character we want to wrap the result with
26
    /// double quotes because the double quoted style allows for arbitrary
27
    /// strings with "\" escape sequences.
28
    ///
29
    /// See 7.3.1 Double-Quoted Style
30
    /// https://yaml.org/spec/1.2/spec.html#id2787109
31
    if (isDangerousString(value)) {
63✔
32
      return _yamlEncodeDoubleQuoted(value);
39✔
33
    }
34

35
    return value;
30✔
36
  }
37

38
  return value.toString();
57✔
39
}
36✔
40

41
/// Checks if [string] has unprintable characters according to
42
/// [unprintableCharCodes].
43
bool _hasUnprintableCharacters(String string) {
63✔
44
  final codeUnits = string.codeUnits;
33✔
45

46
  for (final key in unprintableCharCodes.keys) {
129✔
47
    if (codeUnits.contains(key)) return true;
63✔
48
  }
49

50
  return false;
30✔
51
}
30✔
52

53
/// Generates a YAML-safe double-quoted string based on [string], escaping the
54
/// list of characters as defined by the YAML 1.2 spec.
55
///
56
/// See 5.7 Escaped Characters https://yaml.org/spec/1.2/spec.html#id2776092
57
String _yamlEncodeDoubleQuoted(String string) {
39✔
58
  final buffer = StringBuffer();
21✔
59
  for (final codeUnit in string.codeUnits) {
60✔
60
    if (doubleQuoteEscapeChars[codeUnit] != null) {
60✔
61
      buffer.write(doubleQuoteEscapeChars[codeUnit]);
69✔
62
    } else {
63
      buffer.writeCharCode(codeUnit);
21✔
64
    }
65
  }
66

67
  return '"$buffer"';
39✔
68
}
18✔
69

70
/// Generates a YAML-safe single-quoted string. Automatically escapes
71
/// single-quotes.
72
///
73
/// It is important that we ensure that [string] is free of unprintable
74
/// characters by calling [_hasUnprintableCharacters] before invoking this
75
/// function.
76
String _tryYamlEncodeSingleQuoted(String string) {
18✔
77
  // If [string] contains a newline we'll use double quoted strings instead.
78
  // Single quoted strings can represent newlines, but then we have to use an
79
  // empty line (replace \n with \n\n). But since leading spaces following
80
  // line breaks are ignored, we can't represent "\n ".
81
  // Thus, if the string contains `\n` and we're asked to do single quoted,
82
  // we'll fallback to a double quoted string.
83
  // TODO: Consider if we should make '\n' an unprintedable, this might make
84
  //       folded strings into double quoted -- some work is needed here.
85
  if (string.contains('\n')) {
18✔
86
    return _yamlEncodeDoubleQuoted(string);
6✔
87
  }
88
  final result = string.replaceAll('\'', '\'\'');
9✔
89
  return '\'$result\'';
18✔
90
}
9✔
91

92
/// Generates a YAML-safe folded string.
93
///
94
/// It is important that we ensure that [string] is free of unprintable
95
/// characters by calling [_hasUnprintableCharacters] before invoking this
96
/// function.
97
String _tryYamlEncodeFolded(String string, int indentation, String lineEnding) {
6✔
98
  String result;
99

100
  final trimmedString = string.trimRight();
12✔
101
  final removedPortion = string.substring(trimmedString.length);
18✔
102

103
  if (removedPortion.contains('\n')) {
12✔
NEW
104
    result = '>+\n${' ' * indentation}';
×
105
  } else {
106
    result = '>-\n${' ' * indentation}';
18✔
107
  }
108

109
  /// Duplicating the newline for folded strings preserves it in YAML.
110
  /// Assumes the user did not try to account for windows documents by using
111
  /// `\r\n` already
112
  return result +
6✔
113
      trimmedString.replaceAll('\n', lineEnding * 2 + ' ' * indentation) +
36✔
114
      removedPortion;
115
}
116

117
/// Generates a YAML-safe literal string.
118
///
119
/// It is important that we ensure that [string] is free of unprintable
120
/// characters by calling [_hasUnprintableCharacters] before invoking this
121
/// function.
122
String _tryYamlEncodeLiteral(
9✔
123
    String string, int indentation, String lineEnding) {
124
  final result = '|-\n$string';
9✔
125

126
  /// Assumes the user did not try to account for windows documents by using
127
  /// `\r\n` already
128
  return result.replaceAll('\n', lineEnding + ' ' * indentation);
36✔
129
}
130

131
/// Returns [value] with the necessary formatting applied in a flow context
132
/// if possible.
133
///
134
/// If [value] is a [YamlScalar], we try to respect its [style] parameter where
135
/// possible. Certain cases make this impossible (e.g. a plain string scalar
136
/// that starts with '>'), in which case we will produce [value] with default
137
/// styling options.
138
String _yamlEncodeFlowScalar(YamlNode value) {
63✔
139
  if (value is YamlScalar) {
63✔
140
    assertValidScalar(value.value);
96✔
141

142
    final val = value.value;
63✔
143
    if (val is String) {
63✔
144
      if (_hasUnprintableCharacters(val) ||
51✔
145
          value.style == ScalarStyle.DOUBLE_QUOTED) {
78✔
146
        return _yamlEncodeDoubleQuoted(val);
18✔
147
      }
148

149
      if (value.style == ScalarStyle.SINGLE_QUOTED) {
78✔
150
        return _tryYamlEncodeSingleQuoted(val);
18✔
151
      }
152
    }
153

154
    return _tryYamlEncodePlain(value.value);
96✔
155
  }
156

157
  assertValidScalar(value);
×
158
  return _tryYamlEncodePlain(value);
×
159
}
30✔
160

161
/// Returns [value] with the necessary formatting applied in a block context
162
/// if possible.
163
///
164
/// If [value] is a [YamlScalar], we try to respect its [style] parameter where
165
/// possible. Certain cases make this impossible (e.g. a folded string scalar
166
/// 'null'), in which case we will produce [value] with default styling
167
/// options.
168
String yamlEncodeBlockScalar(
75✔
169
  YamlNode value,
170
  int indentation,
171
  String lineEnding,
172
) {
173
  if (value is YamlScalar) {
75✔
174
    assertValidScalar(value.value);
114✔
175

176
    final val = value.value;
75✔
177
    if (val is String) {
75✔
178
      if (_hasUnprintableCharacters(val)) {
51✔
179
        return _yamlEncodeDoubleQuoted(val);
18✔
180
      }
181

182
      if (value.style == ScalarStyle.SINGLE_QUOTED) {
78✔
183
        return _tryYamlEncodeSingleQuoted(val);
×
184
      }
185

186
      // Strings with only white spaces will cause a misparsing
187
      if (val.trim().length == val.length && val.isNotEmpty) {
159✔
188
        if (value.style == ScalarStyle.FOLDED) {
78✔
189
          return _tryYamlEncodeFolded(val, indentation, lineEnding);
12✔
190
        }
191

192
        if (value.style == ScalarStyle.LITERAL) {
78✔
193
          return _tryYamlEncodeLiteral(val, indentation, lineEnding);
18✔
194
        }
195
      }
196
    }
197

198
    return _tryYamlEncodePlain(value.value);
114✔
199
  }
200

201
  assertValidScalar(value);
×
202

203
  /// The remainder of the possibilities are similar to how [getFlowScalar]
204
  /// treats [value].
205
  return _yamlEncodeFlowScalar(value);
×
206
}
36✔
207

208
/// Returns [value] with the necessary formatting applied in a flow context.
209
///
210
/// If [value] is a [YamlNode], we try to respect its [style] parameter where
211
/// possible. Certain cases make this impossible (e.g. a plain string scalar
212
/// that starts with '>', a child having a block style parameters), in which
213
/// case we will produce [value] with default styling options.
214
String yamlEncodeFlowString(YamlNode value) {
63✔
215
  if (value is YamlList) {
63✔
216
    final list = value.nodes;
18✔
217

218
    final safeValues = list.map(yamlEncodeFlowString);
18✔
219
    return '[${safeValues.join(', ')}]';
27✔
220
  } else if (value is YamlMap) {
63✔
221
    final safeEntries = value.nodes.entries.map((entry) {
57✔
222
      final safeKey = yamlEncodeFlowString(entry.key as YamlNode);
27✔
223
      final safeValue = yamlEncodeFlowString(entry.value);
27✔
224
      return '$safeKey: $safeValue';
18✔
225
    });
9✔
226

227
    return '{${safeEntries.join(', ')}}';
36✔
228
  }
229

230
  return _yamlEncodeFlowScalar(value);
63✔
231
}
30✔
232

233
/// Returns [value] with the necessary formatting applied in a block context.
234
///
235
/// If [value] is a [YamlNode], we respect its [style] parameter.
236
String yamlEncodeBlockString(
75✔
237
  YamlNode value,
238
  int indentation,
239
  String lineEnding,
240
) {
241
  const additionalIndentation = 2;
242

243
  if (!isBlockNode(value)) return yamlEncodeFlowString(value);
90✔
244

245
  final newIndentation = indentation + additionalIndentation;
75✔
246

247
  if (value is YamlList) {
75✔
248
    if (value.isEmpty) return '${' ' * indentation}[]';
24✔
249

250
    Iterable<String> safeValues;
251

252
    final children = value.nodes;
45✔
253

254
    safeValues = children.map((child) {
69✔
255
      var valueString =
21✔
256
          yamlEncodeBlockString(child, newIndentation, lineEnding);
45✔
257
      if (isCollection(child) && !isFlowYamlCollectionNode(child)) {
54✔
258
        valueString = valueString.substring(newIndentation);
12✔
259
      }
260

261
      return '${' ' * indentation}- $valueString';
69✔
262
    });
21✔
263

264
    return safeValues.join(lineEnding);
45✔
265
  } else if (value is YamlMap) {
75✔
266
    if (value.isEmpty) return '${' ' * indentation}{}';
18✔
267

268
    return value.nodes.entries.map((entry) {
87✔
269
      final safeKey = yamlEncodeFlowString(entry.key as YamlNode);
51✔
270
      final formattedKey = ' ' * indentation + safeKey;
51✔
271
      final formattedValue =
272
          yamlEncodeBlockString(entry.value, newIndentation, lineEnding);
51✔
273

274
      /// Empty collections are always encoded in flow-style, so new-line must
275
      /// be avoided
276
      if (isCollection(entry.value) && !isEmpty(entry.value)) {
63✔
277
        return '$formattedKey:\n$formattedValue';
18✔
278
      }
279

280
      return '$formattedKey: $formattedValue';
33✔
281
    }).join(lineEnding);
33✔
282
  }
283

284
  return yamlEncodeBlockScalar(value, newIndentation, lineEnding);
75✔
285
}
36✔
286

287
/// List of unprintable characters.
288
///
289
/// See 5.7 Escape Characters https://yaml.org/spec/1.2/spec.html#id2776092
290
final Map<int, String> unprintableCharCodes = {
135✔
291
  0: '\\0', //  Escaped ASCII null (#x0) character.
292
  7: '\\a', //  Escaped ASCII bell (#x7) character.
293
  8: '\\b', //  Escaped ASCII backspace (#x8) character.
294
  11: '\\v', //         Escaped ASCII vertical tab (#xB) character.
295
  12: '\\f', //  Escaped ASCII form feed (#xC) character.
296
  13: '\\r', //  Escaped ASCII carriage return (#xD) character. Line Break.
297
  27: '\\e', //  Escaped ASCII escape (#x1B) character.
298
  133: '\\N', //  Escaped Unicode next line (#x85) character.
299
  160: '\\_', //  Escaped Unicode non-breaking space (#xA0) character.
300
  8232: '\\L', //  Escaped Unicode line separator (#x2028) character.
301
  8233: '\\P', //  Escaped Unicode paragraph separator (#x2029) character.
302
};
303

304
/// List of escape characters.
305
///
306
/// See 5.7 Escape Characters https://yaml.org/spec/1.2/spec.html#id2776092
307
final Map<int, String> doubleQuoteEscapeChars = {
81✔
308
  ...unprintableCharCodes,
39✔
309
  9: '\\t', //  Escaped ASCII horizontal tab (#x9) character. Printable
21✔
310
  10: '\\n', //  Escaped ASCII line feed (#xA) character. Line Break.
21✔
311
  34: '\\"', //  Escaped ASCII double quote (#x22).
21✔
312
  47: '\\/', //  Escaped ASCII slash (#x2F), for JSON compatibility.
21✔
313
  92: '\\\\', //  Escaped ASCII back slash (#x5C).
21✔
314
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc