Add assist to convert child: to children: in Flutter new-exprs
R=brianwilkerson@google.com, scheglov@google.com
Review-Url: https://codereview.chromium.org/2749283004 .
diff --git a/pkg/analysis_server/lib/src/services/correction/assist.dart b/pkg/analysis_server/lib/src/services/correction/assist.dart
index 3b8ea1c..7158950 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist.dart
@@ -83,6 +83,8 @@
'CONVERT_DOCUMENTATION_INTO_LINE',
30,
"Convert into line documentation comment");
+ static const CONVERT_FLUTTER_CHILD =
+ const AssistKind('CONVERT_FLUTTER_CHILD', 30, "Convert to children:");
static const CONVERT_INTO_BLOCK_BODY = const AssistKind(
'CONVERT_INTO_BLOCK_BODY', 30, "Convert into block body");
static const CONVERT_INTO_EXPRESSION_BODY = const AssistKind(
@@ -119,10 +121,10 @@
"Join 'if' statement with outer 'if' statement");
static const JOIN_VARIABLE_DECLARATION = const AssistKind(
'JOIN_VARIABLE_DECLARATION', 30, "Join variable declaration");
- static const MOVE_FLUTTER_WIDGET_DOWN = const AssistKind(
- "MOVE_FLUTTER_WIDGET_DOWN", 30, "Move widget down");
- static const MOVE_FLUTTER_WIDGET_UP = const AssistKind(
- "MOVE_FLUTTER_WIDGET_UP", 30, "Move widget up");
+ static const MOVE_FLUTTER_WIDGET_DOWN =
+ const AssistKind("MOVE_FLUTTER_WIDGET_DOWN", 30, "Move widget down");
+ static const MOVE_FLUTTER_WIDGET_UP =
+ const AssistKind("MOVE_FLUTTER_WIDGET_UP", 30, "Move widget up");
static const REPARENT_FLUTTER_LIST = const AssistKind(
"REPARENT_FLUTTER_LIST", 30, "Wrap widget list with new widget");
static const REPARENT_FLUTTER_WIDGET = const AssistKind(
diff --git a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
index c343b17..18d32b1 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
@@ -121,6 +121,7 @@
_addProposal_convertDocumentationIntoLine();
_addProposal_convertToBlockFunctionBody();
_addProposal_convertToExpressionFunctionBody();
+ _addProposal_convertFlutterChild();
_addProposal_convertToForIndexLoop();
_addProposal_convertToIsNot_onIs();
_addProposal_convertToIsNot_onNot();
@@ -505,6 +506,73 @@
_addAssist(DartAssistKind.CONVERT_DOCUMENTATION_INTO_LINE, []);
}
+ void _addProposal_convertFlutterChild() {
+ NamedExpression namedExp;
+ // Allow assist to activate from either the new-expr or the child: arg.
+ if (node is SimpleIdentifier &&
+ node.parent is Label &&
+ node.parent.parent is NamedExpression) {
+ namedExp = node.parent.parent as NamedExpression;
+ if ((node as SimpleIdentifier).name != 'child' ||
+ namedExp.expression == null) {
+ return;
+ }
+ if (namedExp.parent?.parent is! InstanceCreationExpression) {
+ return;
+ }
+ InstanceCreationExpression newExpr = namedExp.parent.parent;
+ if (newExpr == null || !_isFlutterInstanceCreationExpression(newExpr)) {
+ return;
+ }
+ } else {
+ InstanceCreationExpression newExpr = _identifyNewExpression();
+ if (newExpr == null || !_isFlutterInstanceCreationExpression(newExpr)) {
+ _coverageMarker();
+ return;
+ }
+ namedExp = _findChildArgument(newExpr);
+ if (namedExp == null || namedExp.expression == null) {
+ _coverageMarker();
+ return;
+ }
+ }
+ InstanceCreationExpression childArg = _getChildWidget(namedExp, false);
+ if (childArg == null) {
+ _coverageMarker();
+ return;
+ }
+ int childLoc = namedExp.offset + 'child'.length;
+ _addInsertEdit(childLoc, 'ren');
+ int listLoc = childArg.offset;
+ String childArgSrc = utils.getNodeText(childArg);
+ if (!childArgSrc.contains(eol)) {
+ _addInsertEdit(listLoc, '<Widget>[');
+ _addInsertEdit(listLoc + childArg.length, ']');
+ } else {
+ int newlineLoc = childArgSrc.lastIndexOf(eol);
+ if (newlineLoc == childArgSrc.length) {
+ newlineLoc -= 1;
+ }
+ String indentOld = utils.getLinePrefix(childArg.offset + 1 + newlineLoc);
+ String indentNew = '$indentOld${utils.getIndent(1)}';
+ // The separator includes 'child:' but that has no newlines.
+ String separator =
+ utils.getText(namedExp.offset, childArg.offset - namedExp.offset);
+ String prefix = separator.contains(eol) ? "" : "$eol$indentNew";
+ if (prefix.isEmpty) {
+ _addInsertEdit(namedExp.offset + 'child:'.length, ' <Widget>[');
+ _addRemoveEdit(rangeStartLength(childArg.offset - 2, 2));
+ } else {
+ _addInsertEdit(listLoc, '<Widget>[');
+ }
+ String newChildArgSrc = childArgSrc.replaceAll(
+ new RegExp("^$indentOld", multiLine: true), "$indentNew");
+ newChildArgSrc = "$prefix$newChildArgSrc,$eol$indentOld]";
+ _addReplaceEdit(rangeNode(childArg), newChildArgSrc);
+ }
+ _addAssist(DartAssistKind.CONVERT_FLUTTER_CHILD, []);
+ }
+
void _addProposal_convertIntoFinalField() {
// Find the enclosing getter.
MethodDeclaration getter;
@@ -2311,11 +2379,12 @@
return _getChildWidget(child);
}
- InstanceCreationExpression _getChildWidget(NamedExpression child) {
+ InstanceCreationExpression _getChildWidget(NamedExpression child,
+ [bool strict = false]) {
if (child?.expression is InstanceCreationExpression) {
InstanceCreationExpression childNewExpr = child.expression;
if (_isFlutterInstanceCreationExpression(childNewExpr)) {
- if (_findChildArgument(childNewExpr) != null) {
+ if (!strict || (_findChildArgument(childNewExpr) != null)) {
return childNewExpr;
}
}
diff --git a/pkg/analysis_server/test/services/correction/assist_test.dart b/pkg/analysis_server/test/services/correction/assist_test.dart
index b52c25d..29b3920 100644
--- a/pkg/analysis_server/test/services/correction/assist_test.dart
+++ b/pkg/analysis_server/test/services/correction/assist_test.dart
@@ -1036,6 +1036,129 @@
''');
}
+ test_convertFlutterChild_OK_multiLine() async {
+ _configureFlutterPkg({
+ 'src/widgets/framework.dart': _flutter_framework_code,
+ });
+ await resolveTestUnit('''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new Center(
+ /*caret*/child: new Container(
+ width: 200.0,
+ height: 300.0,
+ ),
+ key: null,
+ ),
+// end
+ );
+}
+''');
+ _setCaretLocation();
+ await assertHasAssist(
+ DartAssistKind.CONVERT_FLUTTER_CHILD,
+ '''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new Center(
+ /*caret*/children: <Widget>[
+ new Container(
+ width: 200.0,
+ height: 300.0,
+ ),
+ ],
+ key: null,
+ ),
+// end
+ );
+}
+''');
+ }
+
+ test_convertFlutterChild_OK_newlineChild() async {
+ // This case could occur with deeply nested constructors, common in Flutter.
+ _configureFlutterPkg({
+ 'src/widgets/framework.dart': _flutter_framework_code,
+ });
+ await resolveTestUnit('''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new Center(
+ /*caret*/child:
+ new Container(
+ width: 200.0,
+ height: 300.0,
+ ),
+ key: null,
+ ),
+// end
+ );
+}
+''');
+ _setCaretLocation();
+ await assertHasAssist(
+ DartAssistKind.CONVERT_FLUTTER_CHILD,
+ '''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new Center(
+ /*caret*/children: <Widget>[
+ new Container(
+ width: 200.0,
+ height: 300.0,
+ ),
+ ],
+ key: null,
+ ),
+// end
+ );
+}
+''');
+ }
+
+ test_convertFlutterChild_OK_singleLine() async {
+ _configureFlutterPkg({
+ 'src/widgets/framework.dart': _flutter_framework_code,
+ });
+ await resolveTestUnit('''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new Center(
+ /*caret*/child: new GestureDetector(),
+ key: null,
+ ),
+// end
+ );
+}
+''');
+ _setCaretLocation();
+ await assertHasAssist(
+ DartAssistKind.CONVERT_FLUTTER_CHILD,
+ '''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new Center(
+ /*caret*/children: <Widget>[new GestureDetector()],
+ key: null,
+ ),
+// end
+ );
+}
+''');
+ }
+
test_convertToBlockBody_BAD_noEnclosingFunction() async {
await resolveTestUnit('''
var v = 123;
@@ -3502,6 +3625,104 @@
''');
}
+ test_moveFlutterWidgetDown_OK() async {
+ _configureFlutterPkg({
+ 'src/widgets/framework.dart': _flutter_framework_code,
+ });
+ await resolveTestUnit('''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new /*caret*/GestureDetector(
+ onTap: () => startResize(),
+ child: new Center(
+ child: new Container(
+ width: 200.0,
+ height: 300.0,
+ ),
+ key: null,
+ ),
+ ),
+// end
+ );
+}
+startResize() {}
+''');
+ _setCaretLocation();
+ await assertHasAssist(
+ DartAssistKind.MOVE_FLUTTER_WIDGET_DOWN,
+ '''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new Center(
+ child: new /*caret*/GestureDetector(
+ onTap: () => startResize(),
+ child: new Container(
+ width: 200.0,
+ height: 300.0,
+ ),
+ ),
+ key: null,
+ ),
+// end
+ );
+}
+startResize() {}
+''');
+ }
+
+ test_moveFlutterWidgetUp_OK() async {
+ _configureFlutterPkg({
+ 'src/widgets/framework.dart': _flutter_framework_code,
+ });
+ await resolveTestUnit('''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new Center(
+ child: new /*caret*/GestureDetector(
+ onTap: () => startResize(),
+ child: new Container(
+ width: 200.0,
+ height: 300.0,
+ ),
+ ),
+ key: null,
+ ),
+// end
+ );
+}
+startResize() {}
+''');
+ _setCaretLocation();
+ await assertHasAssist(
+ DartAssistKind.MOVE_FLUTTER_WIDGET_UP,
+ '''
+import 'package:flutter/src/widgets/framework.dart';
+build() {
+ return new Scaffold(
+// start
+ body: new /*caret*/GestureDetector(
+ onTap: () => startResize(),
+ child: new Center(
+ child: new Container(
+ width: 200.0,
+ height: 300.0,
+ ),
+ key: null,
+ ),
+ ),
+// end
+ );
+}
+startResize() {}
+''');
+ }
+
test_removeTypeAnnotation_classField_OK() async {
await resolveTestUnit('''
class A {
@@ -3666,104 +3887,6 @@
await assertNoAssist(DartAssistKind.REPARENT_FLUTTER_LIST);
}
- test_moveFlutterWidgetDown_OK() async {
- _configureFlutterPkg({
- 'src/widgets/framework.dart': _flutter_framework_code,
- });
- await resolveTestUnit('''
-import 'package:flutter/src/widgets/framework.dart';
-build() {
- return new Scaffold(
-// start
- body: new /*caret*/GestureDetector(
- onTap: () => startResize(),
- child: new Center(
- child: new Container(
- width: 200.0,
- height: 300.0,
- ),
- key: null,
- ),
- ),
-// end
- );
-}
-startResize() {}
-''');
- _setCaretLocation();
- await assertHasAssist(
- DartAssistKind.MOVE_FLUTTER_WIDGET_DOWN,
- '''
-import 'package:flutter/src/widgets/framework.dart';
-build() {
- return new Scaffold(
-// start
- body: new Center(
- child: new /*caret*/GestureDetector(
- onTap: () => startResize(),
- child: new Container(
- width: 200.0,
- height: 300.0,
- ),
- ),
- key: null,
- ),
-// end
- );
-}
-startResize() {}
-''');
- }
-
- test_moveFlutterWidgetUp_OK() async {
- _configureFlutterPkg({
- 'src/widgets/framework.dart': _flutter_framework_code,
- });
- await resolveTestUnit('''
-import 'package:flutter/src/widgets/framework.dart';
-build() {
- return new Scaffold(
-// start
- body: new Center(
- child: new /*caret*/GestureDetector(
- onTap: () => startResize(),
- child: new Container(
- width: 200.0,
- height: 300.0,
- ),
- ),
- key: null,
- ),
-// end
- );
-}
-startResize() {}
-''');
- _setCaretLocation();
- await assertHasAssist(
- DartAssistKind.MOVE_FLUTTER_WIDGET_UP,
- '''
-import 'package:flutter/src/widgets/framework.dart';
-build() {
- return new Scaffold(
-// start
- body: new /*caret*/GestureDetector(
- onTap: () => startResize(),
- child: new Center(
- child: new Container(
- width: 200.0,
- height: 300.0,
- ),
- key: null,
- ),
- ),
-// end
- );
-}
-startResize() {}
-''');
- }
-
test_reparentFlutterList_OK_multiLine() async {
_configureFlutterPkg({
'src/widgets/framework.dart': _flutter_framework_code,