Elements. Migrate FlutterConvertToStatefulWidget.
Change-Id: Iaacb9e2ccc908c64528ae7e6ee48c7094076b7d4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/389264
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
diff --git a/pkg/analysis_server/analyzer_use_new_elements.txt b/pkg/analysis_server/analyzer_use_new_elements.txt
index ceb17fb..17676a0 100644
--- a/pkg/analysis_server/analyzer_use_new_elements.txt
+++ b/pkg/analysis_server/analyzer_use_new_elements.txt
@@ -317,6 +317,7 @@
lib/src/services/correction/dart/extend_class_for_mixin.dart
lib/src/services/correction/dart/extract_local_variable.dart
lib/src/services/correction/dart/flutter_convert_to_children.dart
+lib/src/services/correction/dart/flutter_convert_to_stateful_widget.dart
lib/src/services/correction/dart/flutter_move_down.dart
lib/src/services/correction/dart/flutter_move_up.dart
lib/src/services/correction/dart/flutter_remove_widget.dart
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/flutter_convert_to_stateful_widget.dart b/pkg/analysis_server/lib/src/services/correction/dart/flutter_convert_to_stateful_widget.dart
index c8f7459..a7b0682 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/flutter_convert_to_stateful_widget.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/flutter_convert_to_stateful_widget.dart
@@ -8,7 +8,7 @@
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
-import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
@@ -43,7 +43,7 @@
}
// Must be a StatelessWidget subclass.
- var widgetClassElement = widgetClass.declaredElement!;
+ var widgetClassElement = widgetClass.declaredFragment!.element;
var superType = widgetClassElement.supertype;
if (superType == null || !superType.isExactlyStatelessWidgetType) {
return;
@@ -70,21 +70,22 @@
// Prepare nodes to move.
var nodesToMove = <ClassMember>{};
- var elementsToMove = <Element>{};
+ var elementsToMove = <Element2>{};
for (var member in widgetClass.members) {
if (member is FieldDeclaration && !member.isStatic) {
for (var fieldNode in member.fields.variables) {
- var fieldElement = fieldNode.declaredElement as FieldElement;
+ var fieldFragment = fieldNode.declaredFragment as FieldFragment;
+ var fieldElement = fieldFragment.element;
if (!fieldsAssignedInConstructors.contains(fieldElement)) {
nodesToMove.add(member);
elementsToMove.add(fieldElement);
- var getter = fieldElement.getter;
+ var getter = fieldElement.getter2;
if (getter != null) {
elementsToMove.add(getter);
}
- var setter = fieldElement.setter;
+ var setter = fieldElement.setter2;
if (setter != null) {
elementsToMove.add(setter);
}
@@ -92,7 +93,7 @@
}
} else if (member is MethodDeclaration && !member.isStatic) {
nodesToMove.add(member);
- elementsToMove.add(member.declaredElement!);
+ elementsToMove.add(member.declaredFragment!.element);
}
}
@@ -112,15 +113,15 @@
}
var statefulWidgetClass =
- await sessionHelper.getFlutterClass('StatefulWidget');
- var stateClass = await sessionHelper.getFlutterClass('State');
+ await sessionHelper.getFlutterClass2('StatefulWidget');
+ var stateClass = await sessionHelper.getFlutterClass2('State');
if (statefulWidgetClass == null || stateClass == null) {
return;
}
await builder.addDartFileEdit(file, (builder) {
builder.addReplacement(range.node(superclass), (builder) {
- builder.writeReference(statefulWidgetClass);
+ builder.writeReference2(statefulWidgetClass);
});
var replaceOffset = 0;
@@ -148,7 +149,7 @@
}
builder.writeln(' @override');
builder.write(' ');
- builder.writeReference(stateClass);
+ builder.writeReference2(stateClass);
builder.write('<${widgetClass.name.lexeme}$typeParams>');
builder.writeln(' createState() => $stateName$typeParams();');
if (hasEmptyLineAfterCreateState) {
@@ -205,7 +206,7 @@
builder.writeln();
builder.write('class $stateName$typeParams extends ');
- builder.writeReference(stateClass);
+ builder.writeReference2(stateClass);
// Write just param names (and not bounds, metadata and docs).
builder.write('<${widgetClass.name.lexeme}');
@@ -262,13 +263,13 @@
}
class _FieldFinder extends RecursiveAstVisitor<void> {
- Set<FieldElement> fieldsAssignedInConstructors = {};
+ Set<FieldElement2> fieldsAssignedInConstructors = {};
@override
void visitFieldFormalParameter(FieldFormalParameter node) {
- var element = node.declaredElement;
- if (element is FieldFormalParameterElement) {
- var field = element.field;
+ var element = node.declaredFragment?.element;
+ if (element is FieldFormalParameterElement2) {
+ var field = element.field2;
if (field != null) {
fieldsAssignedInConstructors.add(field);
}
@@ -280,16 +281,16 @@
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.parent is ConstructorFieldInitializer) {
- var element = node.staticElement;
- if (element is FieldElement) {
+ var element = node.element;
+ if (element is FieldElement2) {
fieldsAssignedInConstructors.add(element);
}
}
if (node.inSetterContext()) {
- var element = node.writeOrReadElement;
- if (element is PropertyAccessorElement) {
- var field = element.variable2;
- if (field is FieldElement) {
+ var element = node.writeOrReadElement2;
+ if (element is SetterElement) {
+ var field = element.variable3;
+ if (field is FieldElement2) {
fieldsAssignedInConstructors.add(field);
}
}
@@ -298,9 +299,9 @@
}
class _ReplacementEditBuilder extends RecursiveAstVisitor<void> {
- final ClassElement widgetClassElement;
+ final ClassElement2 widgetClassElement;
- final Set<Element> elementsToMove;
+ final Set<Element2> elementsToMove;
final SourceRange linesRange;
@@ -314,9 +315,9 @@
if (node.inDeclarationContext()) {
return;
}
- var element = node.staticElement;
- if (element is ExecutableElement &&
- element.enclosingElement3 == widgetClassElement &&
+ var element = node.element;
+ if (element is ExecutableElement2 &&
+ element.enclosingElement2 == widgetClassElement &&
!elementsToMove.contains(element)) {
var offset = node.offset - linesRange.offset;
var qualifier =
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
index 16dd85f..08ab499 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
@@ -948,6 +948,14 @@
}
@override
+ void writeReference2(Element2 element) {
+ if (element.enclosingElement2 is LibraryElement2) {
+ _writeLibraryReference2(element);
+ }
+ write(element.displayName);
+ }
+
+ @override
void writeSetterDeclaration(String name,
{void Function()? bodyWriter,
bool isStatic = false,
@@ -1502,6 +1510,42 @@
}
}
+ /// Writes the import prefix to reference the [element], if needed.
+ ///
+ /// The prefix is not needed if the [element] is defined in the target
+ /// library, or there is already an import without prefix that exports the
+ /// [element]. If there are no existing import that exports the [element], a
+ /// library that exports the [element] is scheduled for import, possibly with
+ /// a prefix.
+ void _writeLibraryReference2(Element2 element) {
+ // If the element is defined in the library, then no prefix needed.
+ if (dartFileEditBuilder._isDefinedLocally2(element)) {
+ return;
+ }
+
+ // TODO(scheglov): We should use "methodBeingCopied" to verify that
+ // we really are just copying this type parameter.
+ if (element is TypeParameterElement2) {
+ return;
+ }
+
+ var import = dartFileEditBuilder._getImportElement2(element);
+ if (import == null) {
+ var library = element.library2?.firstFragment.source.uri;
+ if (library != null) {
+ import = dartFileEditBuilder._importLibrary(library);
+ }
+ }
+ if (import == null) {
+ return;
+ }
+ import.ensureShown(element.name!);
+ var prefix = import.prefix;
+ if (prefix.isNotEmpty) {
+ write('$prefix.');
+ }
+ }
+
void _writeSuperMemberInvocation(ExecutableElement element, String memberName,
List<ParameterElement> parameters) {
var isOperator = element.isOperator;
@@ -1921,6 +1965,10 @@
/// them visible in the generated code.
final Map<Element, _LibraryImport> _elementLibrariesToImport = {};
+ /// A mapping of [Element2]s to pending imports that will be added to make
+ /// them visible in the generated code.
+ final Map<Element2, _LibraryImport> _elementLibrariesToImport2 = {};
+
/// Initializes a newly created builder to build a source file edit within the
/// change being built by the given [changeBuilder].
///
@@ -2001,6 +2049,9 @@
for (var entry in _elementLibrariesToImport.entries) {
copy._elementLibrariesToImport[entry.key] = entry.value;
}
+ for (var entry in _elementLibrariesToImport2.entries) {
+ copy._elementLibrariesToImport2[entry.key] = entry.value;
+ }
return copy;
}
@@ -2696,6 +2747,40 @@
return (libraryChangeBuilder ?? this)._elementLibrariesToImport[element];
}
+ /// Returns information about the library used to import the given [element]
+ /// into the target library, or `null` if the element was not imported, such
+ /// as when the element is declared in the same library.
+ ///
+ /// The result may be an existing import, or one that is pending.
+ _LibraryImport? _getImportElement2(Element2 element) {
+ for (var import
+ in resolvedUnit.libraryElement2.firstFragment.libraryImports2) {
+ var definedNames = import.namespace.definedNames2;
+ if (definedNames.containsValue(element)) {
+ var importedLibrary = import.importedLibrary2;
+ if (importedLibrary != null) {
+ return _LibraryImport(
+ uriText: importedLibrary.firstFragment.source.uri.toString(),
+ isExplicitlyImported: true,
+ shownNames: [
+ for (var combinator in import.combinators)
+ if (combinator is ShowElementCombinator)
+ combinator.shownNames.toList(),
+ ],
+ hiddenNames: [
+ for (var combinator in import.combinators)
+ if (combinator is HideElementCombinator)
+ combinator.hiddenNames.toList(),
+ ],
+ prefix: import.prefix2?.name ?? '',
+ );
+ }
+ }
+ }
+
+ return (libraryChangeBuilder ?? this)._elementLibrariesToImport2[element];
+ }
+
List<LibraryImportElement> _getImportsForUri(Uri uri) {
return [
for (var import
@@ -2829,6 +2914,11 @@
return element.library == resolvedUnit.libraryElement;
}
+ /// Returns whether the [element] is defined in the target library.
+ bool _isDefinedLocally2(Element2 element) {
+ return element.library2 == resolvedUnit.libraryElement2;
+ }
+
/// Removes any pending imports (for [Element]s) that are no longer necessary
/// because the newly-added [newImport] for [newLibrary] also provides those
/// [Element]s.
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
index 4c64883..8fc424d 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
@@ -333,6 +333,13 @@
/// the current library, imports will be updated.
void writeReference(Element element);
+ /// Writes the code that references the [element].
+ ///
+ /// If the [element] is a top-level element that has not been imported into
+ /// the current library, imports will be updated.
+ @experimental
+ void writeReference2(Element2 element);
+
/// Writes the code for a declaration of a setter with the given [name].
///
/// If a [bodyWriter] is provided, it will be invoked to write the body of the