Change 'Extract Widget' to generate named constructor parameters, with 'key'.
R=devoncarew@google.com, paulberry@google.com
Bug: https://github.com/flutter/flutter-intellij/issues/2166
Change-Id: Ie698dc27a1328b7e18e92f46583f84c22c84de57
Reviewed-on: https://dart-review.googlesource.com/53060
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/refactoring/extract_widget.dart b/pkg/analysis_server/lib/src/services/refactoring/extract_widget.dart
index 76d0115..9d52193 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/extract_widget.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/extract_widget.dart
@@ -37,6 +37,7 @@
CorrectionUtils utils;
ClassElement classBuildContext;
+ ClassElement classKey;
ClassElement classStatefulWidget;
ClassElement classStatelessWidget;
ClassElement classWidget;
@@ -96,8 +97,8 @@
_enclosingUnitMember = (_expression ?? _method).getAncestor(
(n) => n is CompilationUnitMember && n.parent is CompilationUnit);
- result.addStatus(await _initializeParameters());
result.addStatus(await _initializeClasses());
+ result.addStatus(await _initializeParameters());
return result;
}
@@ -200,6 +201,7 @@
}
classBuildContext = await getClass('BuildContext');
+ classKey = await getClass('Key');
classStatelessWidget = await getClass('StatelessWidget');
classStatefulWidget = await getClass('StatefulWidget');
classWidget = await getClass('Widget');
@@ -229,6 +231,10 @@
// We added fields, now add the method parameters.
if (_method != null) {
for (var parameter in _method.parameters.parameters) {
+ if (parameter is DefaultFormalParameter) {
+ DefaultFormalParameter defaultFormalParameter = parameter;
+ parameter = defaultFormalParameter.parameter;
+ }
if (parameter is NormalFormalParameter) {
_parameters.add(new _Parameter(
parameter.identifier.name, parameter.element.type,
@@ -237,6 +243,18 @@
}
}
+ RefactoringStatus status = collector.status;
+
+ // If there is an existing parameter "key" warn the user.
+ // We could rename it, but that would require renaming references to it.
+ // It is probably pretty rare, and the user can always rename before.
+ for (var parameter in _parameters) {
+ if (parameter.name == 'key') {
+ status.addError(
+ "The parameter 'key' will conflict with the widget 'key'.");
+ }
+ }
+
return collector.status;
}
@@ -254,29 +272,31 @@
var collector = new _MethodInvocationsCollector(_method.element);
_enclosingClassNode.accept(collector);
for (var invocation in collector.invocations) {
- builder.addReplacement(
- range.startEnd(invocation, invocation.argumentList.leftParenthesis),
- (builder) {
- builder.write('new $name(');
+ List<Expression> arguments = invocation.argumentList.arguments;
+ builder.addReplacement(range.node(invocation), (builder) {
+ builder.write('new $name(');
- // Insert field references.
- for (var parameter in _parameters) {
- if (parameter.isMethodParameter) {
- break;
- }
- if (parameter != _parameters.first) {
- builder.write(', ');
- }
- builder.write(parameter.name);
- }
-
- // Separate references to fields and method arguments.
- if (_parameters.isNotEmpty &&
- invocation.argumentList.arguments.isNotEmpty) {
+ // Insert field references (as named arguments).
+ // Ensure that invocation arguments are named.
+ int argumentIndex = 0;
+ for (var parameter in _parameters) {
+ if (parameter != _parameters.first) {
builder.write(', ');
}
- },
- );
+ builder.write(parameter.name);
+ builder.write(': ');
+ if (parameter.isMethodParameter) {
+ Expression argument = arguments[argumentIndex++];
+ if (argument is NamedExpression) {
+ argument = (argument as NamedExpression).expression;
+ }
+ builder.write(utils.getNodeText(argument));
+ } else {
+ builder.write(parameter.name);
+ }
+ }
+ builder.write(')');
+ });
}
}
@@ -289,8 +309,8 @@
name,
superclass: classStatelessWidget.type,
membersWriter: () {
+ // Add the fields for the parameters.
if (_parameters.isNotEmpty) {
- // Add the fields for the parameters.
for (var parameter in _parameters) {
builder.write(' ');
builder.writeFieldDeclaration(parameter.name,
@@ -298,15 +318,34 @@
builder.writeln();
}
builder.writeln();
-
- // Add the constructor.
- builder.write(' ');
- builder.writeConstructorDeclaration(name,
- fieldNames: _parameters.map((e) => e.name).toList());
- builder.writeln();
- builder.writeln();
}
+ // Add the constructor.
+ builder.write(' ');
+ builder.writeConstructorDeclaration(
+ name,
+ parameterWriter: () {
+ builder.write('{');
+
+ // Add the required `key` parameter.
+ builder.writeParameter('key', type: classKey.type);
+
+ // Add parameters for fields, local, and method parameters.
+ for (int i = 0; i < _parameters.length; i++) {
+ builder.write(', ');
+ builder.write('this.');
+ builder.write(_parameters[i].name);
+ }
+
+ builder.write('}');
+ },
+ initializerWriter: () {
+ builder.write('super(key: key)');
+ },
+ );
+ builder.writeln();
+ builder.writeln();
+
// Widget build(BuildContext context) { ... }
builder.writeln(' @override');
builder.write(' ');
@@ -352,6 +391,8 @@
builder.write(', ');
}
builder.write(parameter.name);
+ builder.write(': ');
+ builder.write(parameter.name);
}
builder.write(')');
@@ -381,7 +422,7 @@
/// Whether the parameter is a parameter of the method being extracted.
final bool isMethodParameter;
- _Parameter(this.name, this.type, {this.isMethodParameter = false});
+ _Parameter(this.name, this.type, {this.isMethodParameter: false});
}
class _ParametersCollector extends RecursiveAstVisitor<void> {
diff --git a/pkg/analysis_server/test/services/refactoring/extract_widget_test.dart b/pkg/analysis_server/test/services/refactoring/extract_widget_test.dart
index 3355620..4e8323b 100644
--- a/pkg/analysis_server/test/services/refactoring/extract_widget_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/extract_widget_test.dart
@@ -130,6 +130,8 @@
}
class Test extends StatelessWidget {
+ Test({Key key}) : super(key: key);
+
@override
Widget build(BuildContext context) {
return new Column(
@@ -178,6 +180,8 @@
}
class Test extends StatelessWidget {
+ Test({Key key}) : super(key: key);
+
@override
Widget build(BuildContext context) {
return new Text('AAA');
@@ -211,6 +215,8 @@
}
class Test extends StatelessWidget {
+ Test({Key key}) : super(key: key);
+
@override
Widget build(BuildContext context) {
return new Container();
@@ -248,6 +254,8 @@
}
class Test extends StatelessWidget {
+ Test({Key key}) : super(key: key);
+
@override
Widget build(BuildContext context) {
return new Text('AAA');
@@ -345,14 +353,14 @@
@override
Widget build(BuildContext context) {
- return new Test(c);
+ return new Test(c: c);
}
}
class Test extends StatelessWidget {
final C c;
- Test(this.c);
+ Test({Key key, this.c}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -400,6 +408,8 @@
}
class Test extends StatelessWidget {
+ Test({Key key}) : super(key: key);
+
@override
Widget build(BuildContext context) {
var a = new Text('AAA');
@@ -453,8 +463,8 @@
int bar = 1;
return new Row(
children: <Widget>[
- new Test(foo, 'aaa', bar),
- new Test(foo, 'bbb', 2),
+ new Test(foo: foo, p1: 'aaa', p2: bar),
+ new Test(foo: foo, p1: 'bbb', p2: 2),
],
);
}
@@ -465,7 +475,74 @@
final String p1;
final int p2;
- Test(this.foo, this.p1, this.p2);
+ Test({Key key, this.foo, this.p1, this.p2}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ var a = new Text('$foo $p1');
+ var b = new Text('$p2');
+ return new Column(
+ children: <Widget>[a, b],
+ );
+ }
+}
+''');
+ }
+
+ test_method_parameters_named() async {
+ addFlutterPackage();
+ await indexTestUnit(r'''
+import 'package:flutter/material.dart';
+
+class MyWidget extends StatelessWidget {
+ String foo;
+
+ @override
+ Widget build(BuildContext context) {
+ int bar = 1;
+ return new Row(
+ children: <Widget>[
+ createColumn(p1: 'aaa', p2: bar),
+ createColumn(p1: 'bbb', p2: 2),
+ ],
+ );
+ }
+
+ Widget createColumn({String p1, int p2}) {
+ var a = new Text('$foo $p1');
+ var b = new Text('$p2');
+ return new Column(
+ children: <Widget>[a, b],
+ );
+ }
+}
+''');
+ _createRefactoringForStringOffset('createColumn({String');
+
+ await _assertSuccessfulRefactoring(r'''
+import 'package:flutter/material.dart';
+
+class MyWidget extends StatelessWidget {
+ String foo;
+
+ @override
+ Widget build(BuildContext context) {
+ int bar = 1;
+ return new Row(
+ children: <Widget>[
+ new Test(foo: foo, p1: 'aaa', p2: bar),
+ new Test(foo: foo, p1: 'bbb', p2: 2),
+ ],
+ );
+ }
+}
+
+class Test extends StatelessWidget {
+ final String foo;
+ final String p1;
+ final int p2;
+
+ Test({Key key, this.foo, this.p1, this.p2}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -503,14 +580,14 @@
@override
Widget build(BuildContext context) {
- return new Test(field);
+ return new Test(field: field);
}
}
class Test extends StatelessWidget {
final String field;
- Test(this.field);
+ Test({Key key, this.field}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -552,14 +629,14 @@
@override
Widget build(BuildContext context) {
- return new Test(c);
+ return new Test(c: c);
}
}
class Test extends StatelessWidget {
final C c;
- Test(this.c);
+ Test({Key key, this.c}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -598,6 +675,8 @@
}
class Test extends StatelessWidget {
+ Test({Key key}) : super(key: key);
+
@override
Widget build(BuildContext context) {
return new Text(field);
@@ -695,14 +774,14 @@
@override
Widget build(BuildContext context) {
- return new Test(c);
+ return new Test(c: c);
}
}
class Test extends StatelessWidget {
final C c;
- Test(this.c);
+ Test({Key key, this.c}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -717,6 +796,25 @@
''');
}
+ test_parameters_key() async {
+ addFlutterPackage();
+ await indexTestUnit(r'''
+import 'package:flutter/material.dart';
+
+class MyWidget extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ String key;
+ return new Text('$key $key');
+ }
+}
+''');
+ _createRefactoringForStringOffset('new Text');
+
+ RefactoringStatus status = await refactoring.checkAllConditions();
+ assertRefactoringStatus(status, RefactoringProblemSeverity.ERROR);
+ }
+
test_parameters_local_read_enclosingScope() async {
addFlutterPackage();
await indexTestUnit(r'''
@@ -739,14 +837,14 @@
@override
Widget build(BuildContext context) {
String local;
- return new Test(local);
+ return new Test(local: local);
}
}
class Test extends StatelessWidget {
final String local;
- Test(this.local);
+ Test({Key key, this.local}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -811,7 +909,7 @@
@override
Widget build(BuildContext context) {
String local;
- return new Test(field, local);
+ return new Test(field: field, local: local);
}
}
@@ -819,7 +917,7 @@
final String field;
final String local;
- Test(this.field, this.local);
+ Test({Key key, this.field, this.local}) : super(key: key);
@override
Widget build(BuildContext context) {