If selection in 'Extract Method' does not cover any node, extract the smallest covering expression.

Two comments.

1. We need also to change the Dart plugin for IntelliJ to not select
   the whole line when selection if empty. The whole line is almost
   never a good choice for Flutter.

2. This is not fully consistent with "Extract Lolal" where we give the
   client the list of possible expressions to extract and the user can
   then choose. Here we use just the smallest one. If we want to make
   it consistent, we will need to enchance the protocol and the plugin.

R=brianwilkerson@google.com, devoncarew@google.com

Change-Id: I87a752234d9c825d78a6d787d2b21d1b252166c2
Reviewed-on: https://dart-review.googlesource.com/41520
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/refactoring/extract_method.dart b/pkg/analysis_server/lib/src/services/refactoring/extract_method.dart
index 65905be20..c1ce1cb 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/extract_method.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/extract_method.dart
@@ -472,13 +472,29 @@
     unit.accept(selectionAnalyzer);
     // May be a fatal error.
     {
-      RefactoringStatus status = selectionAnalyzer.status;
-      if (status.hasFatalError) {
-        return status;
+      if (selectionAnalyzer.status.hasFatalError) {
+        return selectionAnalyzer.status;
       }
     }
+
     List<AstNode> selectedNodes = selectionAnalyzer.selectedNodes;
 
+    // If no selected nodes, extract the smallest covering expression.
+    if (selectedNodes.isEmpty) {
+      for (var node = selectionAnalyzer.coveringNode;
+          node != null;
+          node = node.parent) {
+        if (node is Statement) {
+          break;
+        }
+        if (node is Expression && _isExtractable(range.node(node))) {
+          selectedNodes.add(node);
+          selectionRange = range.node(node);
+          break;
+        }
+      }
+    }
+
     // Check selected nodes.
     if (!selectedNodes.isEmpty) {
       AstNode selectedNode = selectedNodes.first;
diff --git a/pkg/analysis_server/test/services/refactoring/extract_method_test.dart b/pkg/analysis_server/test/services/refactoring/extract_method_test.dart
index a5363ec..4079387 100644
--- a/pkg/analysis_server/test/services/refactoring/extract_method_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/extract_method_test.dart
@@ -1233,6 +1233,30 @@
 ''');
   }
 
+  test_singleExpression_coveringExpression() async {
+    await indexTestUnit('''
+main(int n) {
+  var v = new FooBar(n);
+}
+
+class FooBar {
+  FooBar(int count);
+}
+''');
+    _createRefactoringForStringOffset('Bar(n);');
+    return _assertSuccessfulRefactoring('''
+main(int n) {
+  var v = res(n);
+}
+
+FooBar res(int n) => new FooBar(n);
+
+class FooBar {
+  FooBar(int count);
+}
+''');
+  }
+
   test_singleExpression_dynamic() async {
     await indexTestUnit('''
 dynaFunction() {}
@@ -2867,6 +2891,15 @@
     _createRefactoring(offset, length);
   }
 
+  /**
+   * Creates a new refactoring in [refactoring] at the offset of the given
+   * [search] pattern, and with `0` length.
+   */
+  void _createRefactoringForStringOffset(String search) {
+    int offset = findOffset(search);
+    _createRefactoring(offset, 0);
+  }
+
   void _createRefactoringWithSuffix(String selectionSearch, String suffix) {
     int offset = findOffset(selectionSearch + suffix);
     int length = selectionSearch.length;