[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) {