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>
This commit is contained in:
b-stime 2021-04-12 17:33:06 +00:00 committed by commit-bot@chromium.org
parent f162db3ba7
commit c1339411bb
7 changed files with 229 additions and 0 deletions

View file

@ -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 =

View file

@ -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,

View file

@ -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();
}

View file

@ -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 &&

View file

@ -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});
}

View file

@ -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();
}
);
}
''');
}
}

View file

@ -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();