blob: e818a5114cbaffbeddd857b06aebc78e63e24252 [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';
import 'package:collection/collection.dart';
class AddMissingRequiredArgument extends CorrectionProducer {
/// The name of the parameter that was missing.
String _missingParameterName = '';
@override
// Not a stand-alone fix; requires follow-up actions.
bool get canBeAppliedInBulk => false;
@override
// Not a stand-alone fix; requires follow-up actions.
bool get canBeAppliedToFile => false;
@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();
if (creation != null) {
targetElement = creation.constructorName.staticElement;
argumentList = creation.argumentList;
}
}
}
final diagnostic = this.diagnostic;
if (diagnostic == null) {
return;
}
if (targetElement is ExecutableElement && argumentList != null) {
// Format: "Missing required argument 'foo'."
var messageParts =
diagnostic.problemMessage.messageText(includeUrl: false).split("'");
if (messageParts.length < 2) {
return;
}
_missingParameterName = messageParts[1];
var missingParameter = targetElement.parameters.firstWhereOrNull(
(p) => p.name == _missingParameterName,
);
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 ?? false));
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 != null) {
var cursorPosition = defaultValue.cursorPosition;
if (cursorPosition != null) {
builder.write(defaultValue.text.substring(0, cursorPosition));
builder.selectHere();
builder.write(defaultValue.text.substring(cursorPosition));
} else {
builder.addSimpleLinkedEdit('VALUE', defaultValue.text);
}
} else {
builder.addSimpleLinkedEdit('VALUE', 'null');
}
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);
}
}
});
});
}
}
}