Rename State class when StatefulWidget is renamed.

R=brianwilkerson@google.com

Bug: https://github.com/flutter/flutter-intellij/issues/2545
Change-Id: I247db781e3b44910c823bb66d5f8773dc0d415cc
Reviewed-on: https://dart-review.googlesource.com/76080
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename.dart b/pkg/analysis_server/lib/src/services/refactoring/rename.dart
index aa110e0..3b201fb 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename.dart
@@ -15,6 +15,46 @@
 import 'package:analyzer_plugin/utilities/range_factory.dart';
 
 /**
+ * Helper for renaming one or more [Element]s.
+ */
+class RenameProcessor {
+  final SearchEngine searchEngine;
+  final SourceChange change;
+  final String newName;
+
+  RenameProcessor(this.searchEngine, this.change, this.newName);
+
+  /**
+   * Add the edit that updates the [element] declaration.
+   */
+  void addDeclarationEdit(Element element) {
+    if (element != null) {
+      SourceEdit edit =
+          newSourceEdit_range(range.elementName(element), newName);
+      doSourceChange_addElementEdit(change, element, edit);
+    }
+  }
+
+  /**
+   * Add edits that update [matches].
+   */
+  void addReferenceEdits(List<SearchMatch> matches) {
+    List<SourceReference> references = getSourceReferences(matches);
+    for (SourceReference reference in references) {
+      reference.addEdit(change, newName);
+    }
+  }
+
+  /**
+   * Update the [element] declaration and reference to it.
+   */
+  Future<void> renameElement(Element element) {
+    addDeclarationEdit(element);
+    return searchEngine.searchReferences(element).then(addReferenceEdits);
+  }
+}
+
+/**
  * An abstract implementation of [RenameRefactoring].
  */
 abstract class RenameRefactoringImpl extends RefactoringImpl
@@ -36,27 +76,6 @@
 
   Element get element => _element;
 
-  /**
-   * Adds a [SourceEdit] to update [element] name to [change].
-   */
-  void addDeclarationEdit(Element element) {
-    if (element != null) {
-      SourceEdit edit =
-          newSourceEdit_range(range.elementName(element), newName);
-      doSourceChange_addElementEdit(change, element, edit);
-    }
-  }
-
-  /**
-   * Adds [SourceEdit]s to update [matches] to [change].
-   */
-  void addReferenceEdits(List<SearchMatch> matches) {
-    List<SourceReference> references = getSourceReferences(matches);
-    for (SourceReference reference in references) {
-      reference.addEdit(change, newName);
-    }
-  }
-
   @override
   Future<RefactoringStatus> checkInitialConditions() {
     RefactoringStatus result = new RefactoringStatus();
@@ -100,7 +119,7 @@
   /**
    * Adds individual edits to [change].
    */
-  Future fillChange();
+  Future<void> fillChange();
 
   @override
   bool requiresPreview() {
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
index cf80af9..d085e78 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
@@ -86,20 +86,19 @@
   }
 
   @override
-  Future fillChange() async {
-    // TODO(brianwilkerson) Determine whether this await is necessary.
-    await null;
+  Future<void> fillChange() async {
+    var processor = new RenameProcessor(searchEngine, change, newName);
     // update declarations
     for (Element renameElement in _validator.elements) {
       if (renameElement.isSynthetic && renameElement is FieldElement) {
-        addDeclarationEdit(renameElement.getter);
-        addDeclarationEdit(renameElement.setter);
+        processor.addDeclarationEdit(renameElement.getter);
+        processor.addDeclarationEdit(renameElement.setter);
       } else {
-        addDeclarationEdit(renameElement);
+        processor.addDeclarationEdit(renameElement);
       }
     }
     // update references
-    addReferenceEdits(_validator.references);
+    processor.addReferenceEdits(_validator.references);
     // potential matches
     List<SearchMatch> nameMatches =
         await searchEngine.searchMemberReferences(oldName);
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_constructor.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_constructor.dart
index ff84856..755ebff 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_constructor.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_constructor.dart
@@ -56,7 +56,7 @@
   }
 
   @override
-  Future fillChange() async {
+  Future<void> fillChange() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
     // prepare references
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_import.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_import.dart
index 2ebf57a..53e382b 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_import.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_import.dart
@@ -52,7 +52,7 @@
   }
 
   @override
-  Future fillChange() async {
+  Future<void> fillChange() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
     // update declaration
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_label.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_label.dart
index e8a62c9..643be1f 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_label.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_label.dart
@@ -38,8 +38,8 @@
   }
 
   @override
-  Future fillChange() {
-    addDeclarationEdit(element);
-    return searchEngine.searchReferences(element).then(addReferenceEdits);
+  Future<void> fillChange() {
+    var processor = new RenameProcessor(searchEngine, change, newName);
+    return processor.renameElement(element);
   }
 }
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_library.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_library.dart
index ac8326b..e270f82 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_library.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_library.dart
@@ -40,8 +40,8 @@
   }
 
   @override
-  Future fillChange() {
-    addDeclarationEdit(element);
-    return searchEngine.searchReferences(element).then(addReferenceEdits);
+  Future<void> fillChange() async {
+    var processor = new RenameProcessor(searchEngine, change, newName);
+    await processor.renameElement(element);
   }
 }
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_local.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_local.dart
index ce99486..369b832 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_local.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_local.dart
@@ -75,11 +75,10 @@
   }
 
   @override
-  Future fillChange() async {
-    // TODO(brianwilkerson) Determine whether this await is necessary.
-    await null;
+  Future<void> fillChange() async {
+    var processor = new RenameProcessor(searchEngine, change, newName);
     for (Element element in elements) {
-      addDeclarationEdit(element);
+      processor.addDeclarationEdit(element);
       var references = await searchEngine.searchReferences(element);
 
       // Exclude "implicit" references to optional positional parameters.
@@ -87,7 +86,7 @@
         references.removeWhere((match) => match.sourceRange.length == 0);
       }
 
-      addReferenceEdits(references);
+      processor.addReferenceEdits(references);
     }
   }
 
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_unit_member.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_unit_member.dart
index db3ad4e..88cac3a 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_unit_member.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_unit_member.dart
@@ -13,6 +13,7 @@
 import 'package:analysis_server/src/services/refactoring/rename.dart';
 import 'package:analysis_server/src/services/search/element_visitors.dart';
 import 'package:analysis_server/src/services/search/search_engine.dart';
+import 'package:analysis_server/src/utilities/flutter.dart' as flutter;
 import 'package:analyzer/dart/ast/ast.dart' show Identifier;
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/generated/java_core.dart';
@@ -42,6 +43,13 @@
  * A [Refactoring] for renaming compilation unit member [Element]s.
  */
 class RenameUnitMemberRefactoringImpl extends RenameRefactoringImpl {
+  /// If the [element] is a Flutter `StatefulWidget` declaration, this is the
+  /// corresponding `State` declaration.
+  ClassElement _flutterWidgetState;
+
+  /// If [_flutterWidgetState] is set, this is the new name of it.
+  String _flutterWidgetStateNewName;
+
   RenameUnitMemberRefactoringImpl(
       RefactoringWorkspace workspace, Element element)
       : super(workspace, element);
@@ -61,8 +69,25 @@
   }
 
   @override
-  Future<RefactoringStatus> checkFinalConditions() {
-    return validateRenameTopLevel(searchEngine, element, newName);
+  Future<RefactoringStatus> checkFinalConditions() async {
+    var status = await validateRenameTopLevel(searchEngine, element, newName);
+    if (_flutterWidgetState != null) {
+      _updateFlutterWidgetStateName();
+      status.addStatus(
+        await validateRenameTopLevel(
+          searchEngine,
+          _flutterWidgetState,
+          _flutterWidgetStateNewName,
+        ),
+      );
+    }
+    return status;
+  }
+
+  @override
+  Future<RefactoringStatus> checkInitialConditions() {
+    _findFlutterStateClass();
+    return super.checkInitialConditions();
   }
 
   @override
@@ -84,7 +109,7 @@
   }
 
   @override
-  Future fillChange() {
+  Future<void> fillChange() async {
     // prepare elements
     List<Element> elements = [];
     if (element is PropertyInducingElement && element.isSynthetic) {
@@ -100,11 +125,41 @@
     } else {
       elements.add(element);
     }
-    // update each element
-    return Future.forEach(elements, (Element element) {
-      addDeclarationEdit(element);
-      return searchEngine.searchReferences(element).then(addReferenceEdits);
-    });
+
+    // Rename each element and references to it.
+    var processor = new RenameProcessor(searchEngine, change, newName);
+    for (var element in elements) {
+      await processor.renameElement(element);
+    }
+
+    // If a StatefulWidget is being renamed, rename also its State.
+    if (_flutterWidgetState != null) {
+      _updateFlutterWidgetStateName();
+      await new RenameProcessor(
+        searchEngine,
+        change,
+        _flutterWidgetStateNewName,
+      ).renameElement(_flutterWidgetState);
+    }
+  }
+
+  void _findFlutterStateClass() {
+    if (flutter.isStatefulWidgetDeclaration(element)) {
+      var oldStateName = oldName + 'State';
+      _flutterWidgetState = element.library.getType(oldStateName) ??
+          element.library.getType('_' + oldStateName);
+    }
+  }
+
+  void _updateFlutterWidgetStateName() {
+    if (_flutterWidgetState != null) {
+      _flutterWidgetStateNewName = newName + 'State';
+      // If the State was private, ensure that it stays private.
+      if (_flutterWidgetState.name.startsWith('_') &&
+          !_flutterWidgetStateNewName.startsWith('_')) {
+        _flutterWidgetStateNewName = '_' + _flutterWidgetStateNewName;
+      }
+    }
   }
 }
 
diff --git a/pkg/analysis_server/lib/src/utilities/flutter.dart b/pkg/analysis_server/lib/src/utilities/flutter.dart
index db72512..06c3d3c 100644
--- a/pkg/analysis_server/lib/src/utilities/flutter.dart
+++ b/pkg/analysis_server/lib/src/utilities/flutter.dart
@@ -319,6 +319,17 @@
 }
 
 /**
+ * Return `true` if the given [element] is a [ClassElement] that extends
+ * the Flutter class `StatefulWidget`.
+ */
+bool isStatefulWidgetDeclaration(Element element) {
+  if (element is ClassElement) {
+    return isExactlyStatefulWidgetType(element.supertype);
+  }
+  return false;
+}
+
+/**
  * Return `true` if the given [element] is the Flutter class `Widget`, or its
  * subtype.
  */
diff --git a/pkg/analysis_server/test/services/refactoring/rename_unit_member_test.dart b/pkg/analysis_server/test/services/refactoring/rename_unit_member_test.dart
index e5c22bf..1db7113 100644
--- a/pkg/analysis_server/test/services/refactoring/rename_unit_member_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/rename_unit_member_test.dart
@@ -367,6 +367,129 @@
 ''');
   }
 
+  test_createChange_ClassElement_flutterWidget() async {
+    addFlutterPackage();
+    await indexTestUnit('''
+import 'package:flutter/material.dart';
+
+class TestPage extends StatefulWidget {
+  const TestPage();
+
+  @override
+  TestPageState createState() => new TestPageState();
+}
+
+class TestPageState extends State<TestPage> {
+  @override
+  Widget build(BuildContext context) => null;
+}
+''');
+    createRenameRefactoringAtString('TestPage extends');
+
+    expect(refactoring.refactoringName, 'Rename Class');
+    expect(refactoring.elementKindName, 'class');
+    expect(refactoring.oldName, 'TestPage');
+    refactoring.newName = 'NewPage';
+
+    return assertSuccessfulRefactoring('''
+import 'package:flutter/material.dart';
+
+class NewPage extends StatefulWidget {
+  const NewPage();
+
+  @override
+  NewPageState createState() => new NewPageState();
+}
+
+class NewPageState extends State<NewPage> {
+  @override
+  Widget build(BuildContext context) => null;
+}
+''');
+  }
+
+  test_createChange_ClassElement_flutterWidget_privateBoth() async {
+    addFlutterPackage();
+    await indexTestUnit('''
+import 'package:flutter/material.dart';
+
+class _TestPage extends StatefulWidget {
+  const _TestPage();
+
+  @override
+  _TestPageState createState() => new _TestPageState();
+}
+
+class _TestPageState extends State<_TestPage> {
+  @override
+  Widget build(BuildContext context) => null;
+}
+''');
+    createRenameRefactoringAtString('_TestPage extends');
+
+    expect(refactoring.refactoringName, 'Rename Class');
+    expect(refactoring.elementKindName, 'class');
+    expect(refactoring.oldName, '_TestPage');
+    refactoring.newName = '_NewPage';
+
+    return assertSuccessfulRefactoring('''
+import 'package:flutter/material.dart';
+
+class _NewPage extends StatefulWidget {
+  const _NewPage();
+
+  @override
+  _NewPageState createState() => new _NewPageState();
+}
+
+class _NewPageState extends State<_NewPage> {
+  @override
+  Widget build(BuildContext context) => null;
+}
+''');
+  }
+
+  test_createChange_ClassElement_flutterWidget_privateState() async {
+    addFlutterPackage();
+    await indexTestUnit('''
+import 'package:flutter/material.dart';
+
+class TestPage extends StatefulWidget {
+  const TestPage();
+
+  @override
+  _TestPageState createState() => new _TestPageState();
+}
+
+class _TestPageState extends State<TestPage> {
+  @override
+  Widget build(BuildContext context) => null;
+}
+''');
+    createRenameRefactoringAtString('TestPage extends');
+
+    expect(refactoring.refactoringName, 'Rename Class');
+    expect(refactoring.elementKindName, 'class');
+    expect(refactoring.oldName, 'TestPage');
+    refactoring.newName = 'NewPage';
+
+    return assertSuccessfulRefactoring('''
+import 'package:flutter/material.dart';
+
+class NewPage extends StatefulWidget {
+  const NewPage();
+
+  @override
+  _NewPageState createState() => new _NewPageState();
+}
+
+class _NewPageState extends State<NewPage> {
+  @override
+  Widget build(BuildContext context) => null;
+}
+''');
+  }
+
   test_createChange_ClassElement_invocation() async {
     verifyNoTestUnitErrors = false;
     await indexTestUnit('''