Basic implementation of MOVE_FILE for files

Simple file tests are passing (directories, packages still outstanding).

Bug: https://github.com/dart-lang/sdk/issues/33605
Change-Id: Id9793698050c1a98ad56f511bb515d59d6c769ca
Reviewed-on: https://dart-review.googlesource.com/63740
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Danny Tuppeny <dantup@google.com>
diff --git a/pkg/analysis_server/lib/src/services/refactoring/move_file.dart b/pkg/analysis_server/lib/src/services/refactoring/move_file.dart
index d6d56db..58adda3 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/move_file.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/move_file.dart
@@ -9,9 +9,11 @@
 import 'package:analysis_server/src/services/correction/status.dart';
 import 'package:analysis_server/src/services/refactoring/refactoring.dart';
 import 'package:analysis_server/src/services/refactoring/refactoring_internal.dart';
+import 'package:analysis_server/src/services/search/search_engine.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
 import 'package:path/path.dart' as pathos;
 
 /**
@@ -27,11 +29,6 @@
   String oldFile;
   String newFile;
 
-  SourceChange change;
-  LibraryElement library;
-  String oldLibraryDir;
-  String newLibraryDir;
-
   MoveFileRefactoringImpl(ResourceProvider resourceProvider, this.workspace,
       this.source, this.oldFile)
       : resourceProvider = resourceProvider,
@@ -58,8 +55,47 @@
 
   @override
   Future<SourceChange> createChange() async {
-    // TODO(dantup): Implement!
-    return new SourceChange('Update File References');
+    var changeBuilder =
+        new DartChangeBuilder(workspace.drivers.first.currentSession);
+
+    final drivers =
+        workspace.drivers.where((d) => d.contextRoot.containsFile(newFile));
+    if (drivers.length != 1) {
+      // TODO(dantup): What to do in this case? Should we throw?
+      return changeBuilder.sourceChange;
+    }
+
+    final driver = drivers.first; // The above guarantees there's exactly one.
+    final result = await driver.getResult(oldFile);
+    final element = result?.unit?.element;
+    if (element == null) {
+      return changeBuilder.sourceChange;
+    }
+    final library = element.library;
+
+    // If this element is a library, update outgoing references inside the file.
+    if (library != null && element == library.definingCompilationUnit) {
+      await changeBuilder.addFileEdit(library.source.fullName, (builder) {
+        final oldDir = pathContext.dirname(oldFile);
+        final newDir = pathContext.dirname(newFile);
+        _updateUriReferences(builder, library.imports, oldDir, newDir);
+        _updateUriReferences(builder, library.exports, oldDir, newDir);
+        _updateUriReferences(builder, library.parts, oldDir, newDir);
+      });
+    }
+
+    // Update incoming references to this file
+    List<SearchMatch> matches =
+        await workspace.searchEngine.searchReferences(result.unit.element);
+    List<SourceReference> references = getSourceReferences(matches);
+    for (SourceReference reference in references) {
+      await changeBuilder.addFileEdit(reference.file, (builder) {
+        String newUri = _computeNewUri(reference);
+        builder.addSimpleReplacement(reference.range, "'$newUri'");
+      });
+    }
+
+    return changeBuilder.sourceChange;
   }
 
   @override
@@ -91,23 +127,43 @@
     return true;
   }
 
-  void _updateUriReference(UriReferencedElement element) {
+  void _updateUriReference(DartFileEditBuilder builder,
+      UriReferencedElement element, String oldDir, String newDir) {
     if (!element.isSynthetic) {
       String elementUri = element.uri;
       if (_isRelativeUri(elementUri)) {
-        String elementPath = pathContext.join(oldLibraryDir, elementUri);
-        String newUri = _getRelativeUri(elementPath, newLibraryDir);
+        String elementPath = pathContext.join(oldDir, elementUri);
+        String newUri = _getRelativeUri(elementPath, newDir);
         int uriOffset = element.uriOffset;
         int uriLength = element.uriEnd - uriOffset;
-        doSourceChange_addElementEdit(
-            change, library, new SourceEdit(uriOffset, uriLength, "'$newUri'"));
+        builder.addSimpleReplacement(
+            new SourceRange(uriOffset, uriLength), "'$newUri'");
       }
     }
   }
 
-  void _updateUriReferences(List<UriReferencedElement> elements) {
+  void _updateUriReferences(DartFileEditBuilder builder,
+      List<UriReferencedElement> elements, String oldDir, String newDir) {
     for (UriReferencedElement element in elements) {
-      _updateUriReference(element);
+      _updateUriReference(builder, element, oldDir, newDir);
     }
   }
+
+  /**
+   * Computes the URI to use to reference [newFile] from [reference].
+   */
+  String _computeNewUri(SourceReference reference) {
+    String refDir = pathContext.dirname(reference.file);
+    // try to keep package: URI
+    // if (_isPackageReference(reference)) {
+    //   Source newSource = new NonExistingSource(
+    //       newFile, pathos.toUri(newFile), UriKind.FILE_URI);
+    //   Uri restoredUri = context.sourceFactory.restoreUri(newSource);
+    //   if (restoredUri != null) {
+    //     return restoredUri.toString();
+    //   }
+    // }
+    // if no package: URI, prepare relative
+    return _getRelativeUri(newFile, refDir);
+  }
 }
diff --git a/pkg/analysis_server/test/services/refactoring/move_file_test.dart b/pkg/analysis_server/test/services/refactoring/move_file_test.dart
index d2dd25d..72f5b4b 100644
--- a/pkg/analysis_server/test/services/refactoring/move_file_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/move_file_test.dart
@@ -20,7 +20,6 @@
 class MoveFileTest extends RefactoringTest {
   MoveFileRefactoring refactoring;
 
-  @failingTest
   test_file_containing_imports_exports_parts() async {
     String pathA = '/project/000/1111/a.dart';
     String pathB = '/project/000/1111/b.dart';
@@ -135,7 +134,6 @@
     assertNoFileChange(testFile);
   }
 
-  @failingTest
   test_file_referenced_by_part() async {
     String pathA = '/project/000/1111/a.dart';
     testFile = '/project/000/1111/22/test.dart';
@@ -187,6 +185,14 @@
   }
 
   @failingTest
+  test_renaming_part_that_uses_uri_in_part_of() async {
+    // If the file is a part in a library, and the part-of directive uses a URI
+    // rather than a library name, that will need updating too (if the relative
+    // path to the parent changes).
+    fail('Not yet implemented/tested');
+  }
+
+  @failingTest
   test_projectFolder() async {
     fail('Not yet implemented/tested');
   }
@@ -197,10 +203,27 @@
   }
 
   @failingTest
+  test_folder_outside_workspace_returns_failure() async {
+    fail('Not yet implemented/tested');
+  }
+
+  @failingTest
   test_project_folder_ancestor() async {
     fail('Not yet implemented/tested');
   }
 
+  @failingTest
+  test_nonexistent_file_returns_suitable_failure() async {
+    fail('Not yet implemented/tested');
+  }
+
+  @failingTest
+  test_dart_uris_are_unmodified() async {
+    // TODO(dantup): See _computeNewUri implementation which currently only
+    // handles relative + package: urls (package url handling is also incomplete)
+    fail('Not yet implemented/tested');
+  }
+
   /**
    * Checks that all conditions are OK.
    */