Adds assist for basic Flutter Builder
Fixes issues such as
* https://github.com/flutter/flutter-intellij/issues/814
* https://github.com/dart-lang/sdk/issues/33957 .
Essentially, in Flutter, it's often useful to create a new context in the UI/widget tree as a place to begin a search towards the trunk of the tree for various state structures attached to lower levels of the tree. The basic `Builder` widget is the official means for creating such a context/entry-point.
The patches in this PR are essentially a copy-paste-tweak from two types of existing, similar assists. The main origin was taken from `FlutterWrapStreamBuilder` which wraps client code with a constructor for the StreamBuilder sub-class. The new assist, provided here, is a little more basic/general than the StreamBuilder so it was able to also adopt assertion and test code from some of the other assists for basic widgets.
Closes https://github.com/dart-lang/sdk/pull/45656
https://github.com/dart-lang/sdk/pull/45656
GitOrigin-RevId: 00a5043e5dbd410f24f30f6503711413341dbe7a
Change-Id: I5f93837af571b4974e35da131112f82cd9359697
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194941
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/assist.dart b/pkg/analysis_server/lib/src/services/correction/assist.dart
index 2929cc2..b85a733 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist.dart
@@ -138,6 +138,8 @@
static const FLUTTER_WRAP_GENERIC =
AssistKind('dart.assist.flutter.wrap.generic', 31, 'Wrap with widget...');
+ static const FLUTTER_WRAP_BUILDER = AssistKind(
+ 'dart.assist.flutter.wrap.builder', 32, 'Wrap with Builder');
static const FLUTTER_WRAP_CENTER =
AssistKind('dart.assist.flutter.wrap.center', 32, 'Wrap with Center');
static const FLUTTER_WRAP_COLUMN =
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 ef6a1ef..68c3a9b 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
@@ -51,6 +51,7 @@
import 'package:analysis_server/src/services/correction/dart/flutter_swap_with_child.dart';
import 'package:analysis_server/src/services/correction/dart/flutter_swap_with_parent.dart';
import 'package:analysis_server/src/services/correction/dart/flutter_wrap.dart';
+import 'package:analysis_server/src/services/correction/dart/flutter_wrap_builder.dart';
import 'package:analysis_server/src/services/correction/dart/flutter_wrap_generic.dart';
import 'package:analysis_server/src/services/correction/dart/flutter_wrap_stream_builder.dart';
import 'package:analysis_server/src/services/correction/dart/import_add_show.dart';
@@ -128,6 +129,7 @@
FlutterRemoveWidget.newInstance,
FlutterSwapWithChild.newInstance,
FlutterSwapWithParent.newInstance,
+ FlutterWrapBuilder.newInstance,
FlutterWrapGeneric.newInstance,
FlutterWrapStreamBuilder.newInstance,
ImportAddShow.newInstance,
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/flutter_wrap_builder.dart b/pkg/analysis_server/lib/src/services/correction/dart/flutter_wrap_builder.dart
new file mode 100644
index 0000000..a2b9351
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/flutter_wrap_builder.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart = 2.9
+
+import 'package:analysis_server/src/services/correction/assist.dart';
+import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analyzer_plugin/utilities/assist/assist.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+
+class FlutterWrapBuilder extends CorrectionProducer {
+ @override
+ AssistKind get assistKind => DartAssistKind.FLUTTER_WRAP_BUILDER;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ var widgetExpr = flutter.identifyWidgetExpression(node);
+ if (widgetExpr == null) {
+ return;
+ }
+ if (flutter.isExactWidgetTypeBuilder(widgetExpr.staticType)) {
+ return;
+ }
+ var widgetSrc = utils.getNodeText(widgetExpr);
+
+ var builderElement = await sessionHelper.getClass(
+ flutter.widgetsUri,
+ 'Builder',
+ );
+ if (builderElement == null) {
+ return;
+ }
+
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addReplacement(range.node(widgetExpr), (builder) {
+ builder.writeReference(builderElement);
+
+ builder.writeln('(');
+
+ var indentOld = utils.getLinePrefix(widgetExpr.offset);
+ var indentNew1 = indentOld + utils.getIndent(1);
+ var indentNew2 = indentOld + utils.getIndent(2);
+
+ builder.write(indentNew1);
+ builder.writeln('builder: (context) {');
+
+ widgetSrc = widgetSrc.replaceAll(
+ RegExp('^$indentOld', multiLine: true),
+ indentNew2,
+ );
+ builder.write(indentNew2);
+ builder.write('return $widgetSrc');
+ builder.writeln(';');
+
+ builder.write(indentNew1);
+ builder.writeln('}');
+
+ builder.write(indentOld);
+ builder.write(')');
+ });
+ });
+ }
+
+ /// Return an instance of this class. Used as a tear-off in `AssistProcessor`.
+ static FlutterWrapBuilder newInstance() => FlutterWrapBuilder();
+}
diff --git a/pkg/analysis_server/lib/src/utilities/flutter.dart b/pkg/analysis_server/lib/src/utilities/flutter.dart
index 5e2d6fc..adaef36 100644
--- a/pkg/analysis_server/lib/src/utilities/flutter.dart
+++ b/pkg/analysis_server/lib/src/utilities/flutter.dart
@@ -13,6 +13,7 @@
static final Flutter instance = Flutter();
static const _nameAlign = 'Align';
+ static const _nameBuilder = 'Builder';
static const _nameCenter = 'Center';
static const _nameContainer = 'Container';
static const _namePadding = 'Padding';
@@ -443,6 +444,12 @@
_isExactWidget(type.element, _nameAlign, _uriBasic);
}
+ /// Return `true` if the given [type] is the Flutter class `StreamBuilder`.
+ bool isExactWidgetTypeBuilder(DartType type) {
+ return type is InterfaceType &&
+ _isExactWidget(type.element, _nameBuilder, _uriBasic);
+ }
+
/// Return `true` if the given [type] is the Flutter class `Center`.
bool isExactWidgetTypeCenter(DartType type) {
return type is InterfaceType &&
diff --git a/pkg/analysis_server/test/mock_packages/flutter/lib/src/widgets/basic.dart b/pkg/analysis_server/test/mock_packages/flutter/lib/src/widgets/basic.dart
index 68ecbc6..3a72bfd3 100644
--- a/pkg/analysis_server/test/mock_packages/flutter/lib/src/widgets/basic.dart
+++ b/pkg/analysis_server/test/mock_packages/flutter/lib/src/widgets/basic.dart
@@ -157,3 +157,11 @@
Widget child,
});
}
+
+typedef WidgetBuilder = Widget Function(BuildContext context);
+
+class Builder {
+ final WidgetBuilder builder;
+ const Builder(
+ {Key key, @required this.builder});
+}
diff --git a/pkg/analysis_server/test/src/services/correction/assist/flutter_wrap_builder_test.dart b/pkg/analysis_server/test/src/services/correction/assist/flutter_wrap_builder_test.dart
new file mode 100644
index 0000000..1c8ecdc
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/assist/flutter_wrap_builder_test.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart = 2.9
+
+import 'package:analysis_server/src/services/correction/assist.dart';
+import 'package:analyzer_plugin/utilities/assist/assist.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'assist_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(FlutterWrapBuilderTest);
+ });
+}
+
+@reflectiveTest
+class FlutterWrapBuilderTest extends AssistProcessorTest {
+ @override
+ AssistKind get kind => DartAssistKind.FLUTTER_WRAP_BUILDER;
+
+ @override
+ void setUp() {
+ super.setUp();
+ writeTestPackageConfig(
+ flutter: true,
+ );
+ }
+
+ Future<void> test_aroundBuilder() async {
+ await resolveTestCode('''
+import 'package:flutter/widgets.dart';
+
+main() {
+ /*caret*/Builder(
+ builder: (context) => null,
+ );
+}
+''');
+ await assertNoAssist();
+ }
+
+ Future<void> test_aroundNamedConstructor() async {
+ await resolveTestCode('''
+import 'package:flutter/widgets.dart';
+
+class MyWidget extends StatelessWidget {
+ MyWidget.named();
+
+ Widget build(BuildContext context) => null;
+}
+
+main() {
+ return MyWidget./*caret*/named();
+}
+''');
+ await assertHasAssist('''
+import 'package:flutter/widgets.dart';
+
+class MyWidget extends StatelessWidget {
+ MyWidget.named();
+
+ Widget build(BuildContext context) => null;
+}
+
+main() {
+ return Builder(
+ builder: (context) {
+ return MyWidget.named();
+ }
+ );
+}
+''');
+ }
+
+ Future<void> test_aroundText() async {
+ await resolveTestCode('''
+import 'package:flutter/widgets.dart';
+
+main() {
+ /*caret*/Text('a');
+}
+''');
+ await assertHasAssist('''
+import 'package:flutter/widgets.dart';
+
+main() {
+ Builder(
+ builder: (context) {
+ return Text('a');
+ }
+ );
+}
+''');
+ }
+
+ Future<void> test_assignment() async {
+ await resolveTestCode('''
+import 'package:flutter/widgets.dart';
+
+main() {
+ Widget w;
+ w = /*caret*/Container();
+}
+''');
+ await assertHasAssist('''
+import 'package:flutter/widgets.dart';
+
+main() {
+ Widget w;
+ w = Builder(
+ builder: (context) {
+ return Container();
+ }
+ );
+}
+''');
+ }
+
+ Future<void> test_expressionFunctionBody() async {
+ await resolveTestCode('''
+import 'package:flutter/widgets.dart';
+class FakeFlutter {
+ main() => /*caret*/Container();
+}
+''');
+ await assertHasAssist('''
+import 'package:flutter/widgets.dart';
+class FakeFlutter {
+ main() => Builder(
+ builder: (context) {
+ return Container();
+ }
+ );
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/assist/test_all.dart b/pkg/analysis_server/test/src/services/correction/assist/test_all.dart
index fef565f5..f39e638 100644
--- a/pkg/analysis_server/test/src/services/correction/assist/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/assist/test_all.dart
@@ -54,6 +54,7 @@
import 'flutter_surround_with_set_state_test.dart' as surround_with_set_state;
import 'flutter_swap_with_child_test.dart' as flutter_swap_with_child;
import 'flutter_swap_with_parent_test.dart' as flutter_swap_with_parent;
+import 'flutter_wrap_builder_test.dart' as flutter_wrap_builder;
import 'flutter_wrap_center_test.dart' as flutter_wrap_center;
import 'flutter_wrap_column_test.dart' as flutter_wrap_column;
import 'flutter_wrap_container_test.dart' as flutter_wrap_container;
@@ -139,6 +140,7 @@
flutter_wrap_padding.main();
flutter_wrap_row.main();
flutter_wrap_sized_box.main();
+ flutter_wrap_builder.main();
flutter_wrap_stream_builder.main();
import_add_show.main();
inline_invocation.main();