[analysis_server] Fix Rename+References for forElements outside of blocks

Fixes https://github.com/Dart-Code/Dart-Code/issues/4849

Change-Id: Iceeeab549235622d4fe8c5c129c754cea53012b5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/337741
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/test/lsp/references_test.dart b/pkg/analysis_server/test/lsp/references_test.dart
index 52bdf0f..d0a8f2f 100644
--- a/pkg/analysis_server/test/lsp/references_test.dart
+++ b/pkg/analysis_server/test/lsp/references_test.dart
@@ -91,6 +91,32 @@
     await _checkRanges(content);
   }
 
+  Future<void> test_forEachElement_blockBody() async {
+    final content = '''
+void f(List<int> values) {
+  [for (final val^ue in values) [!value!] * 2];
+}
+''';
+
+    await _checkRanges(content);
+  }
+
+  Future<void> test_forEachElement_expressionBody() async {
+    final content = '''
+Object f() => [for (final val^ue in []) [!value!] * 2];
+''';
+
+    await _checkRanges(content);
+  }
+
+  Future<void> test_forEachElement_topLevel() async {
+    final content = '''
+final a = [for (final val^ue in []) [!value!] * 2];
+''';
+
+    await _checkRanges(content);
+  }
+
   Future<void> test_function_startOfParameterList() async {
     final content = '''
 foo^() {
diff --git a/pkg/analysis_server/test/lsp/rename_test.dart b/pkg/analysis_server/test/lsp/rename_test.dart
index 7c57c95..a27a141 100644
--- a/pkg/analysis_server/test/lsp/rename_test.dart
+++ b/pkg/analysis_server/test/lsp/rename_test.dart
@@ -505,6 +505,43 @@
     return _test_rename_withDocumentChanges(content, 'newOne', expectedContent);
   }
 
+  Future<void> test_rename_forEachElement_blockBody() {
+    const content = '''
+void f(List<int> values) {
+  [for (final val^ue in values) value * 2];
+}
+''';
+    const expectedContent = '''
+void f(List<int> values) {
+  [for (final newName in values) newName * 2];
+}
+''';
+    return _test_rename_withDocumentChanges(
+        content, 'newName', expectedContent);
+  }
+
+  Future<void> test_rename_forEachElement_expressionBody() {
+    const content = '''
+Object f() => [for (final val^ue in []) value * 2];
+''';
+    const expectedContent = '''
+Object f() => [for (final newName in []) newName * 2];
+''';
+    return _test_rename_withDocumentChanges(
+        content, 'newName', expectedContent);
+  }
+
+  Future<void> test_rename_forEachElement_topLevel() {
+    const content = '''
+final a = [for (final val^ue in []) value * 2];
+''';
+    const expectedContent = '''
+final a = [for (final newName in []) newName * 2];
+''';
+    return _test_rename_withDocumentChanges(
+        content, 'newName', expectedContent);
+  }
+
   Future<void> test_rename_function_startOfParameterList() {
     const content = '''
 void f^() {}
diff --git a/pkg/analysis_server/test/search/element_references_test.dart b/pkg/analysis_server/test/search/element_references_test.dart
index 200eb75..dd5f9ad 100644
--- a/pkg/analysis_server/test/search/element_references_test.dart
+++ b/pkg/analysis_server/test/search/element_references_test.dart
@@ -1579,4 +1579,46 @@
     assertHasResult(SearchResultKind.REFERENCE, 'T f;');
     assertHasResult(SearchResultKind.REFERENCE, 'T m()');
   }
+
+  Future<void> test_variable_forEachElement_block() async {
+    addTestFile('''
+void f(List<int> values) {
+  {
+    [for (final value in values) value * 2];
+  }
+}
+''');
+    await findElementReferences(search: 'value in', false);
+    expect(searchElement!.kind, ElementKind.LOCAL_VARIABLE);
+    assertHasResult(SearchResultKind.READ, 'value * 2');
+  }
+
+  Future<void> test_variable_forEachElement_expressionBody() async {
+    addTestFile('''
+Object f() => [for (final value in []) value * 2];
+''');
+    await findElementReferences(search: 'value in', false);
+    expect(searchElement!.kind, ElementKind.LOCAL_VARIABLE);
+    assertHasResult(SearchResultKind.READ, 'value * 2');
+  }
+
+  Future<void> test_variable_forEachElement_functionBody() async {
+    addTestFile('''
+void f(List<int> values) {
+  [for (final value in values) value * 2];
+}
+''');
+    await findElementReferences(search: 'value in', false);
+    expect(searchElement!.kind, ElementKind.LOCAL_VARIABLE);
+    assertHasResult(SearchResultKind.READ, 'value * 2');
+  }
+
+  Future<void> test_variable_forEachElement_topLevelVariable() async {
+    addTestFile('''
+final a = [for (final value in []) value * 2];
+''');
+    await findElementReferences(search: 'value in', false);
+    expect(searchElement!.kind, ElementKind.LOCAL_VARIABLE);
+    assertHasResult(SearchResultKind.READ, 'value * 2');
+  }
 }
diff --git a/pkg/analysis_server/test/services/refactoring/legacy/rename_local_test.dart b/pkg/analysis_server/test/services/refactoring/legacy/rename_local_test.dart
index f110983..c7aa459 100644
--- a/pkg/analysis_server/test/services/refactoring/legacy/rename_local_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/legacy/rename_local_test.dart
@@ -391,6 +391,38 @@
 ''');
   }
 
+  Future<void>
+      test_createChange_localVariable_forEach_element_expressionBody() async {
+    await indexTestUnit('''
+Object f() => [for (final value in []) value * 2];
+''');
+    // configure refactoring
+    createRenameRefactoringAtString('value in');
+    expect(refactoring.refactoringName, 'Rename Local Variable');
+    expect(refactoring.elementKindName, 'local variable');
+    refactoring.newName = 'newName';
+    // validate change
+    return assertSuccessfulRefactoring('''
+Object f() => [for (final newName in []) newName * 2];
+''');
+  }
+
+  Future<void>
+      test_createChange_localVariable_forEach_element_inTopLevel() async {
+    await indexTestUnit('''
+final a = [for (final value in []) value * 2];
+''');
+    // configure refactoring
+    createRenameRefactoringAtString('value in');
+    expect(refactoring.refactoringName, 'Rename Local Variable');
+    expect(refactoring.elementKindName, 'local variable');
+    refactoring.newName = 'newName';
+    // validate change
+    return assertSuccessfulRefactoring('''
+final a = [for (final newName in []) newName * 2];
+''');
+  }
+
   Future<void> test_createChange_localVariable_forEach_statement() async {
     await indexTestUnit('''
 void f(List<int> values) {
diff --git a/pkg/analyzer/lib/src/dart/analysis/search.dart b/pkg/analyzer/lib/src/dart/analysis/search.dart
index 0c0d458..52c9ef4 100644
--- a/pkg/analyzer/lib/src/dart/analysis/search.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/search.dart
@@ -370,7 +370,16 @@
       return _searchReferences_PatternVariable(element, searchedFiles);
     } else if (kind == ElementKind.LABEL ||
         kind == ElementKind.LOCAL_VARIABLE) {
-      return _searchReferences_Local(element, (n) => n is Block, searchedFiles);
+      return _searchReferences_Local(
+          element,
+          (n) =>
+              n is Block ||
+              n is ForElement ||
+              n is FunctionBody ||
+              n is TopLevelVariableDeclaration ||
+              n is SwitchExpression ||
+              n.parent is CompilationUnit,
+          searchedFiles);
     } else if (element is LibraryElement) {
       return _searchReferences_Library(element, searchedFiles);
     } else if (element is ParameterElement) {
@@ -754,7 +763,14 @@
     }
 
     // Prepare the enclosing node.
-    var enclosingNode = node.thisOrAncestorMatching(isRootNode);
+    var enclosingNode = node.thisOrAncestorMatching((node) =>
+        isRootNode(node) || node is ClassMember || node is CompilationUnit);
+    assert(
+      enclosingNode != null && enclosingNode is! CompilationUnit,
+      'Did not find enclosing node for local "${element.name}". '
+      'Perhaps the isRootNode function is missing a condition to locate the '
+      'outermost node where this element is in scope?',
+    );
     if (enclosingNode == null) {
       return const <SearchResult>[];
     }
diff --git a/pkg/analyzer/test/src/dart/analysis/search_test.dart b/pkg/analyzer/test/src/dart/analysis/search_test.dart
index 3df7e475..696fb0f 100644
--- a/pkg/analyzer/test/src/dart/analysis/search_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/search_test.dart
@@ -1638,6 +1638,64 @@
 ''');
   }
 
+  test_searchReferences_LocalVariableElement_inForEachElement_expressionBody() async {
+    await resolveTestCode('''
+Object f() => [
+  for (var v in []) v,
+];
+''');
+    final element = findElement.localVar('v');
+    await assertElementReferencesText(element, r'''
+self::@function::f
+  36 2:21 |v| READ
+''');
+  }
+
+  test_searchReferences_LocalVariableElement_inForEachElement_inBlock() async {
+    await resolveTestCode('''
+Object f() {
+  {
+    return [
+      for (var v in []) v,
+    ];
+  }
+}
+''');
+    final element = findElement.localVar('v');
+    await assertElementReferencesText(element, r'''
+self::@function::f
+  54 4:25 |v| READ
+''');
+  }
+
+  test_searchReferences_LocalVariableElement_inForEachElement_inFunctionBody() async {
+    await resolveTestCode('''
+Object f() {
+  return [
+    for (var v in []) v,
+  ];
+}
+''');
+    final element = findElement.localVar('v');
+    await assertElementReferencesText(element, r'''
+self::@function::f
+  46 3:23 |v| READ
+''');
+  }
+
+  test_searchReferences_LocalVariableElement_inForEachElement_topLevel() async {
+    await resolveTestCode('''
+var x = [
+  for (var v in []) v,
+];
+''');
+    final element = findElement.localVar('v');
+    await assertElementReferencesText(element, r'''
+self::@variable::x
+  30 2:21 |v| READ
+''');
+  }
+
   test_searchReferences_LocalVariableElement_inForEachLoop() async {
     await resolveTestCode('''
 main() {
@@ -2624,6 +2682,22 @@
 ''');
   }
 
+  test_searchReferences_VariablePatternElement_switchExpression_topLevel() async {
+    await resolveTestCode('''
+var f = switch (0) {
+  int v when v > 0 => v + 1 + (v = 2),
+  _ => -1,
+}
+''');
+    final element = findNode.bindPatternVariableElement('int v');
+    await assertElementReferencesText(element, r'''
+self::@variable::f
+  34 2:14 |v| READ
+  43 2:23 |v| READ
+  52 2:32 |v| WRITE
+''');
+  }
+
   test_searchReferences_VariablePatternElement_switchStatement_shared() async {
     await resolveTestCode('''
 void f(Object? x) {