Add 'Convert to multiline string' quick assist.

R=brianwilkerson@google.com

Change-Id: Ieaa893e6ceb64ba41d84189267e0087d042fe4e6
Reviewed-on: https://dart-review.googlesource.com/c/91262
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/assist.dart b/pkg/analysis_server/lib/src/services/correction/assist.dart
index 8601163..9f3595e 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist.dart
@@ -77,6 +77,10 @@
       "Convert to field formal parameter");
   static const CONVERT_TO_INT_LITERAL = const AssistKind(
       'dart.assist.convert.toIntLiteral', 30, "Convert to an int literal");
+  static const CONVERT_TO_MULTILINE_STRING = const AssistKind(
+      'dart.assist.convert.toMultilineString',
+      30,
+      "Convert to multiline string");
   static const CONVERT_TO_NORMAL_PARAMETER = const AssistKind(
       'dart.assist.convert.toConstructorNormalParameter',
       30,
diff --git a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
index 6628711..001180e 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
@@ -99,6 +99,7 @@
     await _addProposal_convertToIsNot_onNot();
     await _addProposal_convertToIsNotEmpty();
     await _addProposal_convertToFieldParameter();
+    await _addProposal_convertToMultilineString();
     await _addProposal_convertToNormalParameter();
     await _addProposal_convertToSingleQuotedString();
     await _addProposal_encapsulateField();
@@ -1266,6 +1267,36 @@
     }
   }
 
+  Future<void> _addProposal_convertToMultilineString() async {
+    var node = this.node;
+    if (node is InterpolationElement) {
+      node = (node as InterpolationElement).parent;
+    }
+    if (node is SingleStringLiteral) {
+      SingleStringLiteral literal = node;
+      if (!literal.isMultiline) {
+        var changeBuilder = _newDartChangeBuilder();
+        await changeBuilder.addFileEdit(file, (builder) {
+          var newQuote = literal.isSingleQuoted ? "'''" : '"""';
+          builder.addReplacement(
+            SourceRange(literal.offset + (literal.isRaw ? 1 : 0), 1),
+            (builder) {
+              builder.writeln(newQuote);
+            },
+          );
+          builder.addSimpleReplacement(
+            SourceRange(literal.end - 1, 1),
+            newQuote,
+          );
+        });
+        _addAssistFromBuilder(
+          changeBuilder,
+          DartAssistKind.CONVERT_TO_MULTILINE_STRING,
+        );
+      }
+    }
+  }
+
   Future<void> _addProposal_convertToSingleQuotedString() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
diff --git a/pkg/analysis_server/test/src/services/correction/assist/convert_to_multiline_string_test.dart b/pkg/analysis_server/test/src/services/correction/assist/convert_to_multiline_string_test.dart
new file mode 100644
index 0000000..36c5f9f
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/assist/convert_to_multiline_string_test.dart
@@ -0,0 +1,122 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// 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 'package:analysis_server/src/services/correction/assist.dart';
+import 'package:analyzer_plugin/utilities/assist/assist.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'assist_processor.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ConvertToMultilineStringTest);
+  });
+}
+
+@reflectiveTest
+class ConvertToMultilineStringTest extends AssistProcessorTest {
+  @override
+  AssistKind get kind => DartAssistKind.CONVERT_TO_MULTILINE_STRING;
+
+  test_doubleQuoted() async {
+    await resolveTestUnit('''
+main() {
+  print("abc");
+}
+''');
+    await assertHasAssistAt('abc', '''
+main() {
+  print("""
+abc""");
+}
+''');
+  }
+
+  test_doubleQuoted_alreadyMultiline() async {
+    await resolveTestUnit('''
+main() {
+  print("""abc""");
+}
+''');
+    await assertNoAssistAt('abc');
+  }
+
+  test_doubleQuoted_interpolation() async {
+    await resolveTestUnit(r"""
+main() {
+  var b = 'b';
+  var c = 'c';
+  print("a $b-${c} d");
+}
+""");
+    await assertHasAssistAt('"a ', r'''
+main() {
+  var b = 'b';
+  var c = 'c';
+  print("""
+a $b-${c} d""");
+}
+''');
+  }
+
+  test_doubleQuoted_raw() async {
+    await resolveTestUnit('''
+main() {
+  print(r"abc");
+}
+''');
+    await assertHasAssistAt('abc', '''
+main() {
+  print(r"""
+abc""");
+}
+''');
+  }
+
+  test_singleQuoted() async {
+    await resolveTestUnit('''
+main() {
+  print('abc');
+}
+''');
+    await assertHasAssistAt('abc', """
+main() {
+  print('''
+abc''');
+}
+""");
+  }
+
+  test_singleQuoted_interpolation() async {
+    await resolveTestUnit(r"""
+main() {
+  var b = 'b';
+  var c = 'c';
+  print('a $b-${c} d');
+}
+""");
+    await assertHasAssistAt("'a ", r"""
+main() {
+  var b = 'b';
+  var c = 'c';
+  print('''
+a $b-${c} d''');
+}
+""");
+  }
+
+  test_singleQuoted_raw() async {
+    await resolveTestUnit('''
+main() {
+  print(r'abc');
+}
+''');
+    await assertHasAssistAt('abc', """
+main() {
+  print(r'''
+abc''');
+}
+""");
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/assist/test_all.dart b/pkg/analysis_server/test/src/services/correction/assist/test_all.dart
index be58fce..a090848 100644
--- a/pkg/analysis_server/test/src/services/correction/assist/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/assist/test_all.dart
@@ -26,6 +26,7 @@
     as convert_to_double_quoted_string;
 import 'convert_to_field_parameter_test.dart' as convert_to_field_parameter;
 import 'convert_to_int_literal_test.dart' as convert_to_int_literal;
+import 'convert_to_multiline_string_test.dart' as convert_to_multiline_string;
 import 'convert_to_normal_parameter_test.dart' as convert_to_normal_parameter;
 import 'convert_to_single_quoted_string_test.dart'
     as convert_to_single_quoted_string;
@@ -88,6 +89,7 @@
     convert_to_double_quoted_string.main();
     convert_to_field_parameter.main();
     convert_to_int_literal.main();
+    convert_to_multiline_string.main();
     convert_to_normal_parameter.main();
     convert_to_single_quoted_string.main();
     encapsulate_field.main();