Implement rename method for Cider

Change-Id: I48d65146b9a7f1687f9afc437aebae3aaa6f533b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/218785
Commit-Queue: Keerti Parthasarathy <keertip@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/cider/rename.dart b/pkg/analysis_server/lib/src/cider/rename.dart
index 6ff6528..0aa1a3f 100644
--- a/pkg/analysis_server/lib/src/cider/rename.dart
+++ b/pkg/analysis_server/lib/src/cider/rename.dart
@@ -14,17 +14,46 @@
 class CanRenameResponse {
   final LineInfo lineInfo;
   final RenameRefactoringElement refactoringElement;
-  final String oldName;
+  final FileResolver _fileResolver;
+  final String filePath;
 
-  CanRenameResponse(this.lineInfo, this.refactoringElement, this.oldName);
+  CanRenameResponse(this.lineInfo, this.refactoringElement, this._fileResolver,
+      this.filePath);
+
+  String get oldName => refactoringElement.element.displayName;
+
+  CheckNameResponse? checkNewName(String name) {
+    var element = refactoringElement.element;
+    RefactoringStatus? status;
+    if (element is LocalVariableElement) {
+      status = validateVariableName(name);
+    } else if (element is ParameterElement) {
+      status = validateParameterName(name);
+    } else if (element is FunctionElement) {
+      status = validateFunctionName(name);
+    }
+    if (status == null) {
+      return null;
+    }
+    return CheckNameResponse(status, this);
+  }
 }
 
 class CheckNameResponse {
-  final LineInfo lineInfo;
   final RefactoringStatus status;
-  final String oldName;
+  final CanRenameResponse canRename;
 
-  CheckNameResponse(this.lineInfo, this.status, this.oldName);
+  CheckNameResponse(this.status, this.canRename);
+
+  LineInfo get lineInfo => canRename.lineInfo;
+
+  String get oldName => canRename.refactoringElement.element.displayName;
+
+  RenameResponse? computeRenameRanges() {
+    var matches = canRename._fileResolver.findReferences(
+        canRename.refactoringElement.offset, canRename.filePath);
+    return RenameResponse(matches, this);
+  }
 }
 
 class CiderRenameComputer {
@@ -56,11 +85,12 @@
     }
     var refactoring = RenameRefactoring.getElementToRename(node, element);
     if (refactoring != null) {
-      return CanRenameResponse(lineInfo, refactoring, element.displayName);
+      return CanRenameResponse(lineInfo, refactoring, _fileResolver, filePath);
     }
     return null;
   }
 
+  @deprecated
   CheckNameResponse? checkNewName(
       String filePath, int line, int column, String name) {
     var resolvedUnit = _fileResolver.resolve(path: filePath);
@@ -74,6 +104,10 @@
       return null;
     }
 
+    var refactoring = RenameRefactoring.getElementToRename(node, element);
+    if (refactoring == null) {
+      return null;
+    }
     RefactoringStatus? status;
     if (element is LocalVariableElement) {
       status = validateVariableName(name);
@@ -85,7 +119,9 @@
     if (status == null) {
       return null;
     }
-    return CheckNameResponse(lineInfo, status, element.displayName);
+
+    return CheckNameResponse(status,
+        CanRenameResponse(lineInfo, refactoring, _fileResolver, filePath));
   }
 
   bool _canRenameElement(Element element) {
@@ -103,3 +139,10 @@
     return false;
   }
 }
+
+class RenameResponse {
+  final List<CiderSearchMatch> matches;
+  final CheckNameResponse checkName;
+
+  RenameResponse(this.matches, this.checkName);
+}
diff --git a/pkg/analysis_server/test/src/cider/rename_test.dart b/pkg/analysis_server/test/src/cider/rename_test.dart
index 028aaea..1a70bf4 100644
--- a/pkg/analysis_server/test/src/cider/rename_test.dart
+++ b/pkg/analysis_server/test/src/cider/rename_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:analysis_server/src/cider/rename.dart';
 import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/micro/resolve_file.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -29,7 +30,6 @@
 }
 ''');
 
-    expect(refactor, isNotNull);
     expect(refactor!.refactoringElement.element.name, 'bar');
     expect(refactor.refactoringElement.offset, _correctionContext.offset);
   }
@@ -40,7 +40,6 @@
 }
 ''');
 
-    expect(refactor, isNotNull);
     expect(refactor!.refactoringElement.element.name, 'foo');
     expect(refactor.refactoringElement.offset, _correctionContext.offset);
   }
@@ -68,7 +67,6 @@
 }
 ''');
 
-    expect(refactor, isNotNull);
     expect(refactor!.refactoringElement.element.name, 'a');
     expect(refactor.refactoringElement.offset, _correctionContext.offset);
   }
@@ -80,7 +78,6 @@
 }
 ''');
 
-    expect(refactor, isNotNull);
     expect(refactor!.refactoringElement.element.name, 'foo');
     expect(refactor.refactoringElement.offset, _correctionContext.offset);
   }
@@ -112,9 +109,8 @@
 int ^foo() => 2;
 ''', 'bar');
 
-    expect(result, isNotNull);
-    expect(result?.status.problems.length, 0);
-    expect(result?.oldName, 'foo');
+    expect(result!.status.problems.length, 0);
+    expect(result.oldName, 'foo');
   }
 
   void test_checkName_local() {
@@ -124,9 +120,8 @@
 }
 ''', 'bar');
 
-    expect(result, isNotNull);
-    expect(result?.status.problems.length, 0);
-    expect(result?.oldName, 'a');
+    expect(result!.status.problems.length, 0);
+    expect(result.oldName, 'a');
   }
 
   void test_checkName_local_invalid() {
@@ -136,9 +131,8 @@
 }
 ''', 'Aa');
 
-    expect(result, isNotNull);
-    expect(result?.status.problems.length, 1);
-    expect(result?.oldName, 'a');
+    expect(result!.status.problems.length, 1);
+    expect(result.oldName, 'a');
   }
 
   void test_checkName_parameter() {
@@ -148,9 +142,57 @@
 }
 ''', 'bar');
 
-    expect(result, isNotNull);
-    expect(result?.status.problems.length, 0);
-    expect(result?.oldName, 'a');
+    expect(result!.status.problems.length, 0);
+    expect(result.oldName, 'a');
+  }
+
+  void test_rename_local() {
+    var result = _rename(r'''
+void foo() {
+  var ^a = 0; var b = a + 1;
+}
+''', 'bar');
+
+    expect(result!.matches.length, 1);
+    expect(
+        result.matches[0],
+        CiderSearchMatch('/workspace/dart/test/lib/test.dart',
+            [CharacterLocation(2, 7), CharacterLocation(2, 22)]));
+  }
+
+  void test_rename_method() {
+    var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
+void foo() {
+  a;
+}
+''');
+    fileResolver.resolve(path: a.path);
+
+    var result = _rename(r'''
+import 'a.dart';
+
+main() {
+^foo();
+}
+''', 'bar');
+
+    expect(result!.matches.length, 2);
+    expect(result.matches, [
+      CiderSearchMatch(
+          '/workspace/dart/test/lib/a.dart', [CharacterLocation(1, 6)]),
+      CiderSearchMatch(
+          '/workspace/dart/test/lib/test.dart', [CharacterLocation(4, 1)])
+    ]);
+  }
+
+  void test_rename_parameter() {
+    var result = _rename(r'''
+void foo(String ^a) {
+  var b = a + 1;
+}
+''', 'bar');
+    expect(result!.matches.length, 1);
+    expect(result.checkName.oldName, 'a');
   }
 
   CheckNameResponse? _checkName(String content, String newName) {
@@ -158,12 +200,13 @@
 
     return CiderRenameComputer(
       fileResolver,
-    ).checkNewName(
-      convertPath(testPath),
-      _correctionContext.line,
-      _correctionContext.character,
-      newName,
-    );
+    )
+        .canRename(
+          convertPath(testPath),
+          _correctionContext.line,
+          _correctionContext.character,
+        )
+        ?.checkNewName(newName);
   }
 
   CanRenameResponse? _compute(String content) {
@@ -178,6 +221,21 @@
     );
   }
 
+  RenameResponse? _rename(String content, newName) {
+    _updateFile(content);
+
+    return CiderRenameComputer(
+      fileResolver,
+    )
+        .canRename(
+          convertPath(testPath),
+          _correctionContext.line,
+          _correctionContext.character,
+        )
+        ?.checkNewName(newName)
+        ?.computeRenameRanges();
+  }
+
   void _updateFile(String content) {
     var offset = content.indexOf('^');
     expect(offset, isPositive, reason: 'Expected to find ^');
diff --git a/pkg/analyzer/lib/source/line_info.dart b/pkg/analyzer/lib/source/line_info.dart
index 1867017..b552a35 100644
--- a/pkg/analyzer/lib/source/line_info.dart
+++ b/pkg/analyzer/lib/source/line_info.dart
@@ -17,6 +17,12 @@
   CharacterLocation(this.lineNumber, this.columnNumber);
 
   @override
+  bool operator ==(Object object) =>
+      object is CharacterLocation &&
+      lineNumber == object.lineNumber &&
+      columnNumber == object.columnNumber;
+
+  @override
   String toString() => '$lineNumber:$columnNumber';
 }
 
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index 89c1fe0..b9fb939 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -9,6 +9,7 @@
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
 import 'package:analyzer/src/context/packages.dart';
 import 'package:analyzer/src/dart/analysis/context_root.dart';
@@ -46,19 +47,20 @@
 
 class CiderSearchMatch {
   final String path;
-  final List<int> offsets;
+  final List<CharacterLocation?> startPositions;
 
-  CiderSearchMatch(this.path, this.offsets);
+  CiderSearchMatch(this.path, this.startPositions);
 
   @override
   bool operator ==(Object object) =>
       object is CiderSearchMatch &&
       path == object.path &&
-      const ListEquality<int>().equals(offsets, object.offsets);
+      const ListEquality<CharacterLocation?>()
+          .equals(startPositions, object.startPositions);
 
   @override
   String toString() {
-    return '($path, $offsets)';
+    return '($path, $startPositions)';
   }
 }
 
@@ -202,7 +204,9 @@
         resolved.unit.accept(collector);
         var offsets = collector.offsets;
         if (offsets.isNotEmpty) {
-          references.add(CiderSearchMatch(filePath, offsets));
+          var lineInfo = resolved.unit.lineInfo;
+          references.add(CiderSearchMatch(filePath,
+              offsets.map((offset) => lineInfo?.getLocation(offset)).toList()));
         }
       });
     }
diff --git a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
index 7a04577..52861ce 100644
--- a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
+++ b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
@@ -2,6 +2,7 @@
 // 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:analyzer/source/line_info.dart';
 import 'package:analyzer/src/dart/error/syntactic_errors.dart';
 import 'package:analyzer/src/dart/micro/cider_byte_store.dart';
 import 'package:analyzer/src/dart/micro/library_graph.dart';
@@ -392,8 +393,8 @@
     await resolveFile(bPath);
     var result = fileResolver.findReferences(6, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [6]),
-      CiderSearchMatch(bPath, [42])
+      CiderSearchMatch(bPath, [CharacterLocation(4, 11)]),
+      CiderSearchMatch(aPath, [CharacterLocation(1, 7)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -413,7 +414,8 @@
     await resolveFile(aPath);
     var result = fileResolver.findReferences(16, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [16, 53])
+      CiderSearchMatch(
+          aPath, [CharacterLocation(2, 7), CharacterLocation(5, 5)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -431,7 +433,8 @@
     await resolveFile(aPath);
     var result = fileResolver.findReferences(11, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [11, 28])
+      CiderSearchMatch(
+          aPath, [CharacterLocation(2, 3), CharacterLocation(5, 1)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -456,8 +459,8 @@
     await resolveFile(bPath);
     var result = fileResolver.findReferences(20, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [20]),
-      CiderSearchMatch(bPath, [56])
+      CiderSearchMatch(bPath, [CharacterLocation(5, 15)]),
+      CiderSearchMatch(aPath, [CharacterLocation(2, 11)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -475,7 +478,8 @@
     await resolveFile(aPath);
     var result = fileResolver.findReferences(39, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [39, 62])
+      CiderSearchMatch(
+          aPath, [CharacterLocation(3, 9), CharacterLocation(4, 11)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -507,8 +511,9 @@
     await resolveFile(bPath);
     var result = fileResolver.findReferences(17, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [17, 68]),
-      CiderSearchMatch(bPath, [46])
+      CiderSearchMatch(bPath, [CharacterLocation(5, 5)]),
+      CiderSearchMatch(
+          aPath, [CharacterLocation(2, 8), CharacterLocation(7, 4)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -533,8 +538,8 @@
     await resolveFile(bPath);
     var result = fileResolver.findReferences(21, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [21]),
-      CiderSearchMatch(bPath, [46])
+      CiderSearchMatch(bPath, [CharacterLocation(5, 5)]),
+      CiderSearchMatch(aPath, [CharacterLocation(2, 12)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -560,8 +565,8 @@
     await resolveFile(bPath);
     var result = fileResolver.findReferences(19, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [19]),
-      CiderSearchMatch(bPath, [39])
+      CiderSearchMatch(bPath, [CharacterLocation(4, 13)]),
+      CiderSearchMatch(aPath, [CharacterLocation(3, 9)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -587,8 +592,8 @@
     await resolveFile(bPath);
     var result = fileResolver.findReferences(20, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [20]),
-      CiderSearchMatch(bPath, [29])
+      CiderSearchMatch(bPath, [CharacterLocation(4, 3)]),
+      CiderSearchMatch(aPath, [CharacterLocation(3, 10)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -607,7 +612,8 @@
     await resolveFile(aPath);
     var result = fileResolver.findReferences(10, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [10, 43])
+      CiderSearchMatch(
+          aPath, [CharacterLocation(1, 11), CharacterLocation(4, 11)])
     ];
     expect(result, unorderedEquals(expected));
   }
@@ -624,12 +630,16 @@
     await resolveFile(aPath);
     var result = fileResolver.findReferences(10, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [10, 22, 40])
+      CiderSearchMatch(aPath, [
+        CharacterLocation(1, 11),
+        CharacterLocation(2, 8),
+        CharacterLocation(4, 12)
+      ])
     ];
     expect(result.map((e) => e.path),
         unorderedEquals(expected.map((e) => e.path)));
-    expect(result.map((e) => e.offsets),
-        unorderedEquals(expected.map((e) => e.offsets)));
+    expect(result.map((e) => e.startPositions),
+        unorderedEquals(expected.map((e) => e.startPositions)));
   }
 
   test_findReferences_typedef() async {
@@ -648,8 +658,8 @@
     await resolveFile(bPath);
     var result = fileResolver.findReferences(8, aPath);
     var expected = <CiderSearchMatch>[
-      CiderSearchMatch(aPath, [8]),
-      CiderSearchMatch(bPath, [25])
+      CiderSearchMatch(bPath, [CharacterLocation(3, 8)]),
+      CiderSearchMatch(aPath, [CharacterLocation(1, 9)])
     ];
     expect(result, unorderedEquals(expected));
   }