[analysis_server] FlutterRemoveWidget to handle Builder

Fixes #49949

Change-Id: I6b6b0454661523f622256199f1ebd4051e37ec0e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/259590
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Ahmed Ashour 2022-09-20 17:04:35 +00:00 committed by Commit Bot
parent 7d46dac715
commit de68893488
4 changed files with 142 additions and 14 deletions

View file

@ -6,10 +6,14 @@ import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/extensions.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/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
import 'package:collection/collection.dart';
class FlutterRemoveWidget extends CorrectionProducer {
@override
@ -47,25 +51,42 @@ class FlutterRemoveWidget extends CorrectionProducer {
} else {
var childArgument = flutter.findChildArgument(widgetCreation);
if (childArgument != null) {
await _removeChild(builder, widgetCreation, childArgument);
await _removeSingle(builder, widgetCreation, childArgument.expression);
} else {
var builderArgument = flutter.findBuilderArgument(widgetCreation);
if (builderArgument != null) {
await _removeBuilder(builder, widgetCreation, builderArgument);
}
}
}
}
Future<void> _removeChild(
Future<void> _removeBuilder(
ChangeBuilder builder,
InstanceCreationExpression widgetCreation,
NamedExpression childArgument) async {
// child: ThisWidget(child: ourChild)
// children: [foo, ThisWidget(child: ourChild), bar]
await builder.addDartFileEdit(file, (builder) {
var childExpression = childArgument.expression;
var childText = utils.getNodeText(childExpression);
var indentOld = utils.getLinePrefix(childExpression.offset);
var indentNew = utils.getLinePrefix(widgetCreation.offset);
childText = replaceSourceIndent(childText, indentOld, indentNew);
builder.addSimpleReplacement(range.node(widgetCreation), childText);
});
NamedExpression builderArgument) async {
var builderExpression = builderArgument.expression;
if (builderExpression is! FunctionExpression) return;
var parameterElement =
builderExpression.parameters?.parameters.firstOrNull?.declaredElement;
if (parameterElement == null) return;
var visitor = _UsageFinder(parameterElement);
var body = builderExpression.body;
body.visitChildren(visitor);
if (visitor.used) return;
if (body is BlockFunctionBody) {
var statements = body.block.statements;
if (statements.length != 1) return;
var statement = statements.first;
if (statement is! ReturnStatement) return;
var expression = statement.expression;
if (expression == null) return;
await _removeSingle(builder, widgetCreation, expression);
} else if (body is ExpressionFunctionBody) {
await _removeSingle(builder, widgetCreation, body.expression);
}
}
Future<void> _removeChildren(
@ -88,4 +109,32 @@ class FlutterRemoveWidget extends CorrectionProducer {
builder.addSimpleReplacement(range.node(widgetCreation), childText);
});
}
Future<void> _removeSingle(
ChangeBuilder builder,
InstanceCreationExpression widgetCreation,
Expression expression,
) async {
await builder.addDartFileEdit(file, (builder) {
var childText = utils.getNodeText(expression);
var indentOld = utils.getLinePrefix(expression.offset);
var indentNew = utils.getLinePrefix(widgetCreation.offset);
childText = replaceSourceIndent(childText, indentOld, indentNew);
builder.addSimpleReplacement(range.node(widgetCreation), childText);
});
}
}
class _UsageFinder extends RecursiveAstVisitor<void> {
final Element element;
bool used = false;
_UsageFinder(this.element);
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.writeOrReadElement == element) {
used = true;
}
}
}

View file

@ -113,6 +113,17 @@ class Flutter {
}
}
/// Return the named expression representing the `builder` argument of the
/// given [newExpr], or `null` if none.
NamedExpression? findBuilderArgument(InstanceCreationExpression newExpr) {
for (var argument in newExpr.argumentList.arguments) {
if (isBuilderArgument(argument)) {
return argument as NamedExpression;
}
}
return null;
}
/// Return the named expression representing the `child` argument of the given
/// [newExpr], or `null` if none.
NamedExpression? findChildArgument(InstanceCreationExpression newExpr) {
@ -278,6 +289,10 @@ class Flutter {
return null;
}
/// Return `true` is the given [argument] is the `builder` argument.
bool isBuilderArgument(Expression argument) =>
argument is NamedExpression && argument.name.label.name == 'builder';
/// Return `true` is the given [argument] is the `child` argument.
bool isChildArgument(Expression argument) =>
argument is NamedExpression && argument.name.label.name == 'child';

View file

@ -162,7 +162,8 @@ class Transform extends SingleChildRenderObjectWidget {
typedef WidgetBuilder = Widget Function(BuildContext context);
class Builder {
class Builder extends StatelessWidget {
final WidgetBuilder builder;
const Builder({Key? key, @required this.builder});
}

View file

@ -33,6 +33,69 @@ class FlutterRemoveWidgetTest extends AssistProcessorTest {
);
}
Future<void> test_builder_blockFunctionBody() async {
await resolveTestCode('''
import 'package:flutter/material.dart';
void f() {
/*caret*/Builder(
builder: (context) {
return Text('');
}
);
}
''');
await assertHasAssist('''
import 'package:flutter/material.dart';
void f() {
Text('');
}
''');
}
Future<void> test_builder_blockFunctionBody_many_statements() async {
await resolveTestCode('''
import 'package:flutter/material.dart';
void f() {
/*caret*/Builder(
builder: (context) {
var i = 1;
return Text('');
}
);
}
''');
await assertNoAssist();
}
Future<void> test_builder_expressionFunctionBody() async {
await resolveTestCode('''
import 'package:flutter/material.dart';
void f() {
/*caret*/Builder(
builder: (context) => Text('')
);
}
''');
await assertHasAssist('''
import 'package:flutter/material.dart';
void f() {
Text('');
}
''');
}
Future<void> test_builder_parameter_used() async {
await resolveTestCode('''
import 'package:flutter/material.dart';
void f() {
/*caret*/Builder(
builder: (context) => context.widget
);
}
''');
await assertNoAssist();
}
Future<void> test_childIntoChild_multiLine() async {
await resolveTestCode('''
import 'package:flutter/material.dart';