@@ -4158,6 +4158,8 @@ class SourceVisitor extends ThrowingAstVisitor {
4158
4158
_endBody (rightBracket, forceSplit: nodes.isNotEmpty);
4159
4159
}
4160
4160
4161
+ static final _lineTerminatorRE = RegExp (r'\r\n?|\n' );
4162
+
4161
4163
/// Writes the string literal [string] to the output.
4162
4164
///
4163
4165
/// Splits multiline strings into separate chunks so that the line splitter
@@ -4167,21 +4169,96 @@ class SourceVisitor extends ThrowingAstVisitor {
4167
4169
// comments are written first.
4168
4170
writePrecedingCommentsAndNewlines (string);
4169
4171
4170
- // Split each line of a multiline string into separate chunks.
4171
- var lines = string.lexeme.split (_formatter.lineEnding! );
4172
+ var lines = string.lexeme.split (_lineTerminatorRE);
4172
4173
var offset = string.offset;
4174
+ var firstLine = lines.first;
4175
+ if (lines.length > 1 ) {
4176
+ // Special case for multiline string which contains
4177
+ // at least one newline.
4178
+ _writeStringFirstLine (firstLine, string, offset: offset);
4179
+ } else {
4180
+ _writeText (firstLine, string, offset: offset);
4181
+ }
4182
+ offset += firstLine.length;
4173
4183
4174
- _writeText (lines.first, string, offset: offset);
4175
- offset += lines.first.length;
4176
-
4177
- for (var line in lines.skip (1 )) {
4184
+ for (var i = 1 ; i < lines.length; i++ ) {
4185
+ var line = lines[i];
4178
4186
builder.writeNewline (flushLeft: true , nest: true );
4179
4187
offset++ ;
4180
4188
_writeText (line, string, offset: offset, mergeEmptySplits: false );
4181
4189
offset += line.length;
4182
4190
}
4183
4191
}
4184
4192
4193
+ /// Writes the first line of a multi-line string.
4194
+ ///
4195
+ /// If the string is a multiline string, and it has only whitespace
4196
+ /// and escaped whitespace before a first line break,
4197
+ /// omit the non-escaped trailing whitespace.
4198
+ /// Normalize escaped non-final whitspace to spaces.
4199
+ ///
4200
+ /// More specifically:
4201
+ /// If a multiline string literal contains at least one line-break
4202
+ /// (a CR, LF or CR+LF) as part of the source character content
4203
+ /// (characters inside interpolation expressions do not count),
4204
+ /// and the source characters from the starting quote to the first
4205
+ /// line-break contains only the characters space, tab and backslash,
4206
+ /// with no two adjacent backslashes, then that part of the string source,
4207
+ /// including the following line break, is excluded from particiapting
4208
+ /// code points to the string value.
4209
+ ///
4210
+ /// This function normalizes such excluded character sequences
4211
+ /// to just the back-slashes, separated by space characters.
4212
+ void _writeStringFirstLine (String line, Token string, {required int offset}) {
4213
+ // Detect leading whitespace on the first line of multiline strings.
4214
+ var quoteStart = line.startsWith ('r' ) ? 1 : 0 ;
4215
+ var quoteEnd = quoteStart + 3 ;
4216
+ var backslashCount = 0 ;
4217
+ if (line.length > quoteEnd &&
4218
+ (line.startsWith ("'''" , quoteStart) ||
4219
+ line.startsWith ('"""' , quoteStart))) {
4220
+ // Start of a multiline string literal.
4221
+ // Check if rest of the line is whitespace, possibly preceded by
4222
+ // backslash, or has a single trailing backslash preceding the newline.
4223
+ // Count the backslashes.
4224
+ var cursor = quoteEnd;
4225
+ const backslash = 0x5c ;
4226
+ const space = 0x20 ;
4227
+ const tab = 0x09 ;
4228
+
4229
+ do {
4230
+ var char = line.codeUnitAt (cursor);
4231
+ if (char == backslash) {
4232
+ cursor += 1 ;
4233
+ backslashCount++ ;
4234
+ if (cursor >= line.length) {
4235
+ break ;
4236
+ }
4237
+ char = line.codeUnitAt (cursor);
4238
+ }
4239
+ if (char != space && char != tab) break ;
4240
+ cursor++ ;
4241
+ } while (cursor < line.length);
4242
+ if (cursor == line.length) {
4243
+ // No invalid character sequence found before end of line.
4244
+ // Normalize the ignored "escaped" whitespace which has no
4245
+ // effect on string content.
4246
+ var firstLineText = line.substring (0 , quoteEnd);
4247
+ if (backslashCount > 0 ) {
4248
+ var buffer = StringBuffer (firstLineText);
4249
+ buffer.write (r'\' );
4250
+ while (-- backslashCount > 0 ) {
4251
+ buffer.write (r' \' );
4252
+ }
4253
+ firstLineText = buffer.toString ();
4254
+ }
4255
+ _writeText (firstLineText, string, offset: offset);
4256
+ return ;
4257
+ }
4258
+ }
4259
+ _writeText (line, string, offset: offset);
4260
+ }
4261
+
4185
4262
/// Write the comma token following [node] , if there is one.
4186
4263
void _writeCommaAfter (AstNode node) {
4187
4264
token (node.commaAfter);
0 commit comments