Tests for completion with nested blocks, functions, and methods.

R=brianwilkerson@google.com

Change-Id: I050aaa2f80a3374fd92587a0b8461bc170078bd8
Reviewed-on: https://dart-review.googlesource.com/55903
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/domains/execution/completion.dart b/pkg/analysis_server/lib/src/domains/execution/completion.dart
index c2ac622..df93f288 100644
--- a/pkg/analysis_server/lib/src/domains/execution/completion.dart
+++ b/pkg/analysis_server/lib/src/domains/execution/completion.dart
@@ -57,6 +57,7 @@
     var contextDir = pathContext.dirname(contextFile);
     var targetPath = pathContext.join(contextDir, '_runtimeCompletion.dart');
 
+    // TODO(scheglov) Use variables.
     await _initContext();
 
     String baseTargetCode = r'''
@@ -156,23 +157,15 @@
   }
 
   void _appendLocals(AstNode node) {
-    void appendParameters(FormalParameterList parameters) {
-      if (parameters != null) {
-        for (var parameter in parameters.parameters) {
-          VariableElement element = parameter.element;
-          locals[element.name] ??= element;
-        }
-      }
-    }
-
     if (node is Block) {
       for (var statement in node.statements) {
+        if (statement.offset > contextOffset) {
+          break;
+        }
         if (statement is VariableDeclarationStatement) {
           for (var variable in statement.variables.variables) {
             VariableElement element = variable.element;
-            if (element.nameOffset < contextOffset) {
-              locals[element.name] ??= element;
-            }
+            locals[element.name] ??= element;
           }
         }
       }
@@ -182,14 +175,19 @@
     } else if (node is CompilationUnit) {
       return;
     } else if (node is FunctionDeclaration) {
-      // TODO(scheglov) test
-      appendParameters(node.functionExpression.parameters);
+      _appendParameters(node.functionExpression.parameters);
     } else if (node is MethodDeclaration) {
-      // TODO(scheglov) test
-      appendParameters(node.parameters);
-    } else {
-      _appendLocals(node.parent);
+      _appendParameters(node.parameters);
     }
-    // TODO(scheglov) support method/function/class
+    _appendLocals(node.parent);
+  }
+
+  void _appendParameters(FormalParameterList parameters) {
+    if (parameters != null) {
+      for (var parameter in parameters.parameters) {
+        VariableElement element = parameter.element;
+        locals[element.name] ??= element;
+      }
+    }
   }
 }
diff --git a/pkg/analysis_server/test/src/domains/execution/completion_test.dart b/pkg/analysis_server/test/src/domains/execution/completion_test.dart
index 8ca1d93..986dc65 100644
--- a/pkg/analysis_server/test/src/domains/execution/completion_test.dart
+++ b/pkg/analysis_server/test/src/domains/execution/completion_test.dart
@@ -33,14 +33,21 @@
         reason: "Not found '// context line'.");
   }
 
-  void assertSuggest(String completion) {
-    expect(result.suggestions, isNotNull);
-    for (var suggestion in result.suggestions) {
-      if (suggestion.completion == completion) {
-        return;
-      }
+  void assertNotSuggested(String completion) {
+    CompletionSuggestion suggestion = getSuggest(completion);
+    if (suggestion != null) {
+      failedCompletion('unexpected $completion');
     }
-    failedCompletion('expected $completion');
+  }
+
+  void assertSuggested(String completion, {String returnType}) {
+    CompletionSuggestion suggestion = getSuggest(completion);
+    if (suggestion == null) {
+      failedCompletion('expected $completion');
+    }
+    if (returnType != null) {
+      expect(suggestion.returnType, returnType);
+    }
   }
 
   Future<void> computeCompletion(
@@ -78,6 +85,16 @@
     fail(sb.toString());
   }
 
+  CompletionSuggestion getSuggest(String completion) {
+    expect(result.suggestions, isNotNull);
+    for (var suggestion in result.suggestions) {
+      if (suggestion.completion == completion) {
+        return suggestion;
+      }
+    }
+    return null;
+  }
+
   test_locals_block() async {
     addContextFile(r'''
 class A {
@@ -90,12 +107,12 @@
 }
 ''');
     await computeCompletion('a.^');
-    assertSuggest('foo');
+    assertSuggested('foo');
 
     // There was an issue with cleaning up
     // Check that the second time it works too.
     await computeCompletion('a.^');
-    assertSuggest('foo');
+    assertSuggested('foo');
   }
 
   test_locals_block_codeWithClosure() async {
@@ -106,6 +123,94 @@
 }
 ''');
     await computeCompletion('items.forEach((e) => e.^)');
-    assertSuggest('toUpperCase');
+    assertSuggested('toUpperCase');
+  }
+
+  test_locals_nested() async {
+    addContextFile(r'''
+void main() {
+  var a = 0;
+  var b = 0.0;
+  {
+    var a = '';
+    // context line
+  }
+  var c = 0;
+}
+''');
+    await computeCompletion('^');
+    assertSuggested('a', returnType: 'String');
+    assertSuggested('b', returnType: 'double');
+
+    // "c" is defined after the context offset, so is not visible.
+    assertNotSuggested('c');
+  }
+
+  test_parameters_function() async {
+    addContextFile(r'''
+void main(int a, double b) {
+  // context line
+}
+''');
+    await computeCompletion('^');
+    assertSuggested('a', returnType: 'int');
+    assertSuggested('b', returnType: 'double');
+  }
+
+  test_parameters_function_locals() async {
+    addContextFile(r'''
+void main(int a, int b) {
+  String a;
+  double c;
+  // context line
+}
+''');
+    await computeCompletion('^');
+    assertSuggested('a', returnType: 'String');
+    assertSuggested('b', returnType: 'int');
+    assertSuggested('c', returnType: 'double');
+  }
+
+  test_parameters_function_nested() async {
+    addContextFile(r'''
+void foo(int a, double b) {
+  void bar(String a, bool c) {
+    // context line
+  }
+}
+''');
+    await computeCompletion('^');
+    assertSuggested('a', returnType: 'String');
+    assertSuggested('b', returnType: 'double');
+    assertSuggested('c', returnType: 'bool');
+  }
+
+  test_parameters_method() async {
+    addContextFile(r'''
+class C {
+  void main(int a, double b) {
+    // context line
+  }
+}
+''');
+    await computeCompletion('^');
+    assertSuggested('a', returnType: 'int');
+    assertSuggested('b', returnType: 'double');
+  }
+
+  test_parameters_method_locals() async {
+    addContextFile(r'''
+class C {
+  void main(int a, int b) {
+    String a;
+    double c;
+    // context line
+  }
+}
+''');
+    await computeCompletion('^');
+    assertSuggested('a', returnType: 'String');
+    assertSuggested('b', returnType: 'int');
+    assertSuggested('c', returnType: 'double');
   }
 }