From c1339411bb27a3538a1a28d62ba19f2ffce47d89 Mon Sep 17 00:00:00 2001 From: b-stime <34577613+b-stime@users.noreply.github.com> Date: Mon, 12 Apr 2021 17:33:06 +0000 Subject: [PATCH] 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 Commit-Queue: Brian Wilkerson --- .../lib/src/services/correction/assist.dart | 2 + .../services/correction/assist_internal.dart | 2 + .../correction/dart/flutter_wrap_builder.dart | 68 +++++++++ .../lib/src/utilities/flutter.dart | 7 + .../flutter/lib/src/widgets/basic.dart | 8 + .../assist/flutter_wrap_builder_test.dart | 140 ++++++++++++++++++ .../services/correction/assist/test_all.dart | 2 + 7 files changed, 229 insertions(+) create mode 100644 pkg/analysis_server/lib/src/services/correction/dart/flutter_wrap_builder.dart create mode 100644 pkg/analysis_server/test/src/services/correction/assist/flutter_wrap_builder_test.dart diff --git a/pkg/analysis_server/lib/src/services/correction/assist.dart b/pkg/analysis_server/lib/src/services/correction/assist.dart index 2929cc2b8b4..b85a733fb64 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 @@ class DartAssistKind { 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 ef6a1efb2dc..68c3a9bf992 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_remove_widg 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 @@ class AssistProcessor extends BaseProcessor { 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 00000000000..a2b9351bbcb --- /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 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 5e2d6fce8f6..adaef3642aa 100644 --- a/pkg/analysis_server/lib/src/utilities/flutter.dart +++ b/pkg/analysis_server/lib/src/utilities/flutter.dart @@ -13,6 +13,7 @@ class Flutter { 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 @@ class Flutter { _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 68ecbc6d921..3a72bfd3d1d 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 @@ class Transform extends SingleChildRenderObjectWidget { 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 00000000000..1c8ecdc0c74 --- /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 test_aroundBuilder() async { + await resolveTestCode(''' +import 'package:flutter/widgets.dart'; + +main() { + /*caret*/Builder( + builder: (context) => null, + ); +} +'''); + await assertNoAssist(); + } + + Future 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 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 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 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 fef565f53f3..f39e638590d 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_remove_widget_test.dart' as flutter_remove_widget; 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 @@ void main() { 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();