blob: 40e8f6f5109e04a55dca654256d7acd896cd78f7 [file] [log] [blame]
// Copyright (c) 2020, 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.
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
import 'package:analysis_server/src/services/completion/dart/utilities.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/element/element.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
class AddMissingRequiredArgument extends CorrectionProducer {
/// The name of the parameter that was missing.
String _missingParameterName;
@override
List<Object> get fixArguments => [_missingParameterName];
@override
FixKind get fixKind => DartFixKind.ADD_MISSING_REQUIRED_ARGUMENT;
@override
Future<void> compute(ChangeBuilder builder) async {
InstanceCreationExpression creation;
Element targetElement;
ArgumentList argumentList;
if (node is SimpleIdentifier || node is ConstructorName) {
var invocation = node.parent;
if (invocation is MethodInvocation) {
targetElement = invocation.methodName.staticElement;
argumentList = invocation.argumentList;
} else {
creation =
invocation.thisOrAncestorOfType<InstanceCreationExpression>();
if (creation != null) {
targetElement = creation.constructorName.staticElement;
argumentList = creation.argumentList;
}
}
}
if (targetElement is ExecutableElement) {
// Format: "Missing required argument 'foo'."
var messageParts = diagnostic.problemMessage.message.split("'");
if (messageParts.length < 2) {
return;
}
_missingParameterName = messageParts[1];
var missingParameter = targetElement.parameters.firstWhere(
(p) => p.name == _missingParameterName,
orElse: () => null);
if (missingParameter == null) {
return;
}
int offset;
var hasTrailingComma = false;
var insertBetweenParams = false;
List<Expression> arguments = argumentList.arguments;
if (arguments.isEmpty) {
offset = argumentList.leftParenthesis.end;
} else {
var lastArgument = arguments.last;
offset = lastArgument.end;
hasTrailingComma = lastArgument.endToken.next.type == TokenType.COMMA;
if (lastArgument is NamedExpression &&
flutter.isWidgetExpression(creation)) {
if (flutter.isChildArgument(lastArgument) ||
flutter.isChildrenArgument(lastArgument)) {
offset = lastArgument.offset;
hasTrailingComma = true;
insertBetweenParams = true;
}
}
}
var defaultValue = getDefaultStringParameterValue(missingParameter,
withNullability: libraryElement.isNonNullableByDefault &&
missingParameter.library.isNonNullableByDefault);
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(offset, (builder) {
if (arguments.isNotEmpty && !insertBetweenParams) {
builder.write(', ');
}
builder.write('$_missingParameterName: ');
// Use defaultValue.cursorPosition if it's not null.
if (defaultValue?.cursorPosition != null) {
builder.write(
defaultValue.text.substring(0, defaultValue.cursorPosition));
builder.selectHere();
builder.write(
defaultValue.text.substring(defaultValue.cursorPosition));
} else {
builder.addSimpleLinkedEdit('VALUE', defaultValue?.text);
}
if (flutter.isWidgetExpression(creation)) {
// Insert a trailing comma after Flutter instance creation params.
if (!hasTrailingComma) {
builder.write(',');
} else if (insertBetweenParams) {
builder.writeln(',');
// Insert indent before the child: or children: param.
var indent = utils.getLinePrefix(offset);
builder.write(indent);
}
}
});
});
}
}
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static AddMissingRequiredArgument newInstance() =>
AddMissingRequiredArgument();
}