[Analyzer] Send a "content modified" response if files are modified during a refactor

See https://github.com/dart-lang/sdk/issues/43459.

Change-Id: I2e32ba0ba1020d18ea574caac42e79ec184a5562
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/165362
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Danny Tuppeny <danny@tuppeny.com>
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
index 714c7ad..dc30e56 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
@@ -12,6 +12,7 @@
 import 'package:analysis_server/src/protocol_server.dart';
 import 'package:analysis_server/src/services/refactoring/refactoring.dart';
 import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/analysis/session.dart';
 
 final _manager = _RefactorManager();
 
@@ -89,6 +90,9 @@
 
           final edit = createWorkspaceEdit(server, change.edits);
           return await sendWorkspaceEditToClient(edit);
+        } on InconsistentAnalysisException {
+          return error(ErrorCodes.ContentModified,
+              'Content was modified before refactor was applied');
         } finally {
           _manager.end(cancellationToken);
         }
diff --git a/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart b/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart
index 19589b0..90bd497 100644
--- a/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart
+++ b/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart
@@ -105,6 +105,31 @@
     expect(contents[mainFilePath], equals(expectedContent));
   }
 
+  Future<void> test_contentModified() async {
+    const content = '''
+main() {
+  print('Test!');
+  [[print('Test!');]]
+}
+    ''';
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(content));
+
+    final codeActions = await getCodeActions(mainFileUri.toString(),
+        range: rangeFromMarkers(content));
+    final codeAction =
+        findCommand(codeActions, Commands.performRefactor, extractMethodTitle);
+    expect(codeAction, isNotNull);
+
+    // Send an edit request immediately after the refactor request.
+    final req1 = executeCodeAction(codeAction);
+    await replaceFile(100, mainFileUri, 'new test content');
+
+    // Expect the first to fail because of the modified content.
+    await expectLater(
+        req1, throwsA(isResponseError(ErrorCodes.ContentModified)));
+  }
+
   Future<void> test_invalidLocation() async {
     const content = '''
 import 'dart:convert';