Use the right line-ending when writing multi-line strings. (#1519)

Use the right line-ending when writing multi-line strings.

There were tests for this already, but those tests were only run using
the short style, so I also fixed that test file to run all of those
tests in both styles.

Fix #1504.
diff --git a/lib/src/back_end/code.dart b/lib/src/back_end/code.dart
index 6b3c1e6..47c06eb 100644
--- a/lib/src/back_end/code.dart
+++ b/lib/src/back_end/code.dart
@@ -54,13 +54,19 @@
 
   /// Traverse the [Code] tree and build the final formatted string.
   ///
+  /// Whenever a newline is written, writes [lineEnding]. If omitted, defaults
+  /// to '\n'.
+  ///
   /// Returns the formatted string and the selection markers if there are any.
-  ({String code, int? selectionStart, int? selectionEnd}) build() {
+  ({String code, int? selectionStart, int? selectionEnd}) build(
+      [String? lineEnding]) {
+    lineEnding ??= '\n';
+
     var buffer = StringBuffer();
     int? selectionStart;
     int? selectionEnd;
 
-    _build(buffer, (marker, offset) {
+    _build(buffer, lineEnding, (marker, offset) {
       if (marker == _Marker.start) {
         selectionStart = offset;
       } else {
@@ -75,7 +81,7 @@
     );
   }
 
-  void _build(StringBuffer buffer,
+  void _build(StringBuffer buffer, String lineEnding,
       void Function(_Marker marker, int offset) markSelection) {
     for (var i = 0; i < _children.length; i++) {
       var child = _children[i];
@@ -83,8 +89,8 @@
         case _NewlineCode():
           // Don't write any leading newlines at the top of the buffer.
           if (i > 0) {
-            buffer.writeln();
-            if (child._blank) buffer.writeln();
+            buffer.write(lineEnding);
+            if (child._blank) buffer.write(lineEnding);
           }
 
           buffer.write(_indents[child._indent] ?? (' ' * child._indent));
@@ -93,7 +99,7 @@
           buffer.write(child._text);
 
         case GroupCode():
-          child._build(buffer, markSelection);
+          child._build(buffer, lineEnding, markSelection);
 
         case _MarkerCode():
           markSelection(child._marker, buffer.length + child._offset);
diff --git a/lib/src/front_end/piece_writer.dart b/lib/src/front_end/piece_writer.dart
index 8450ce0..90e7595 100644
--- a/lib/src/front_end/piece_writer.dart
+++ b/lib/src/front_end/piece_writer.dart
@@ -387,10 +387,11 @@
     Profile.begin('PieceWriter.finish() format piece tree');
 
     var cache = SolutionCache();
-    var formatter = Solver(cache,
+    var solver = Solver(cache,
         pageWidth: _formatter.pageWidth, leadingIndent: _formatter.indent);
-    var solution = formatter.format(rootPiece);
-    var (:code, :selectionStart, :selectionEnd) = solution.code.build();
+    var solution = solver.format(rootPiece);
+    var (:code, :selectionStart, :selectionEnd) =
+        solution.code.build(_formatter.lineEnding);
 
     Profile.end('PieceWriter.finish() format piece tree');
 
diff --git a/test/dart_formatter_test.dart b/test/dart_formatter_test.dart
index 8015a03..f914000 100644
--- a/test/dart_formatter_test.dart
+++ b/test/dart_formatter_test.dart
@@ -3,25 +3,46 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:dart_style/dart_style.dart';
+import 'package:dart_style/src/constants.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:test/test.dart';
 
 void main() async {
+  group('short style', () {
+    _runTests(isTall: false);
+  });
+
+  group('tall style', () {
+    _runTests(isTall: true);
+  });
+}
+
+/// Run all of the DartFormatter tests either using short or tall style.
+void _runTests({required bool isTall}) {
+  DartFormatter makeFormatter(
+      {Version? languageVersion, int? indent, String? lineEnding}) {
+    return DartFormatter(
+        languageVersion: languageVersion,
+        indent: indent,
+        lineEnding: lineEnding,
+        experimentFlags: [if (isTall) tallStyleExperimentFlag]);
+  }
+
   group('language version', () {
     test('defaults to latest if omitted', () {
-      var formatter = DartFormatter();
+      var formatter = makeFormatter();
       expect(formatter.languageVersion, DartFormatter.latestLanguageVersion);
     });
 
     test('defaults to latest if null', () {
-      var formatter = DartFormatter(languageVersion: null);
+      var formatter = makeFormatter(languageVersion: null);
       expect(formatter.languageVersion, DartFormatter.latestLanguageVersion);
     });
 
     test('parses at given older language version', () {
       // Use a language version before patterns were supported and a pattern
       // is an error.
-      var formatter = DartFormatter(languageVersion: Version(2, 19, 0));
+      var formatter = makeFormatter(languageVersion: Version(2, 19, 0));
       expect(() => formatter.format('main() {switch (o) {case var x: break;}}'),
           throwsA(isA<FormatterException>()));
     });
@@ -29,7 +50,7 @@
     test('parses at given newer language version', () {
       // Use a language version after patterns were supported and `1 + 2` is an
       // error.
-      var formatter = DartFormatter(languageVersion: Version(3, 0, 0));
+      var formatter = makeFormatter(languageVersion: Version(3, 0, 0));
       expect(() => formatter.format('main() {switch (o) {case 1+2: break;}}'),
           throwsA(isA<FormatterException>()));
     });
@@ -37,7 +58,7 @@
     test('@dart comment overrides version', () {
       // Use a language version after patterns were supported and `1 + 2` is an
       // error.
-      var formatter = DartFormatter(languageVersion: Version(3, 0, 0));
+      var formatter = makeFormatter(languageVersion: Version(3, 0, 0));
 
       // But then have the code opt to the older version.
       const before = '''
@@ -60,7 +81,7 @@
   });
 
   test('throws a FormatterException on failed parse', () {
-    var formatter = DartFormatter();
+    var formatter = makeFormatter();
     expect(() => formatter.format('wat?!'), throwsA(isA<FormatterException>()));
   });
 
@@ -68,14 +89,14 @@
     // This is a regression test for #358 where an error whose position is
     // past the end of the source caused FormatterException to throw.
     expect(
-        () => DartFormatter().format('library'),
+        () => makeFormatter().format('library'),
         throwsA(isA<FormatterException>().having(
             (e) => e.message(), 'message', contains('Could not format'))));
   });
 
   test('FormatterException describes parse errors', () {
     expect(() {
-      DartFormatter().format('''
+      makeFormatter().format('''
 
       var a = some error;
 
@@ -92,25 +113,25 @@
   });
 
   test('adds newline to unit', () {
-    expect(DartFormatter().format('var x = 1;'), equals('var x = 1;\n'));
+    expect(makeFormatter().format('var x = 1;'), equals('var x = 1;\n'));
   });
 
   test('adds newline to unit after trailing comment', () {
-    expect(DartFormatter().format('library foo; //zamm'),
+    expect(makeFormatter().format('library foo; //zamm'),
         equals('library foo; //zamm\n'));
   });
 
   test('removes extra newlines', () {
-    expect(DartFormatter().format('var x = 1;\n\n\n'), equals('var x = 1;\n'));
+    expect(makeFormatter().format('var x = 1;\n\n\n'), equals('var x = 1;\n'));
   });
 
   test('does not add newline to statement', () {
-    expect(DartFormatter().formatStatement('var x = 1;'), equals('var x = 1;'));
+    expect(makeFormatter().formatStatement('var x = 1;'), equals('var x = 1;'));
   });
 
   test('fails if anything is after the statement', () {
     expect(
-        () => DartFormatter().formatStatement('var x = 1;;'),
+        () => makeFormatter().formatStatement('var x = 1;;'),
         throwsA(isA<FormatterException>()
             .having((e) => e.errors.length, 'errors.length', equals(1))
             .having((e) => e.errors.first.offset, 'errors.length.first.offset',
@@ -118,7 +139,7 @@
   });
 
   test('preserves initial indent', () {
-    var formatter = DartFormatter(indent: 3);
+    var formatter = makeFormatter(indent: 3);
     expect(
         formatter.formatStatement('if (foo) {bar;}'),
         equals('   if (foo) {\n'
@@ -131,29 +152,30 @@
       // Use zero width no-break space character as the line ending. We have
       // to use a whitespace character for the line ending as the formatter
       // will throw an error if it accidentally makes non-whitespace changes
-      // as will occur
+      // as would occur if we used a non-whitespace character as the line
+      // ending.
       var lineEnding = '\t';
-      expect(DartFormatter(lineEnding: lineEnding).format('var i = 1;'),
+      expect(makeFormatter(lineEnding: lineEnding).format('var i = 1;'),
           equals('var i = 1;\t'));
     });
 
     test('infers \\r\\n if the first newline uses that', () {
-      expect(DartFormatter().format('var\r\ni\n=\n1;\n'),
+      expect(makeFormatter().format('var\r\ni\n=\n1;\n'),
           equals('var i = 1;\r\n'));
     });
 
     test('infers \\n if the first newline uses that', () {
-      expect(DartFormatter().format('var\ni\r\n=\r\n1;\r\n'),
+      expect(makeFormatter().format('var\ni\r\n=\r\n1;\r\n'),
           equals('var i = 1;\n'));
     });
 
     test('defaults to \\n if there are no newlines', () {
-      expect(DartFormatter().format('var i =1;'), equals('var i = 1;\n'));
+      expect(makeFormatter().format('var i =1;'), equals('var i = 1;\n'));
     });
 
     test('handles Windows line endings in multiline strings', () {
       expect(
-          DartFormatter(lineEnding: '\r\n').formatStatement('  """first\r\n'
+          makeFormatter(lineEnding: '\r\n').formatStatement('  """first\r\n'
               'second\r\n'
               'third"""  ;'),
           equals('"""first\r\n'
@@ -165,7 +187,7 @@
   test('throws an UnexpectedOutputException on non-whitespace changes', () {
     // Use an invalid line ending character to ensure the formatter will
     // attempt to make non-whitespace changes.
-    var formatter = DartFormatter(lineEnding: '%');
+    var formatter = makeFormatter(lineEnding: '%');
     expect(() => formatter.format('var i = 1;'),
         throwsA(isA<UnexpectedOutputException>()));
   });