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('''