Issue 21965. Add Quick Fix for adding missing named parameter.

R=brianwilkerson@google.com

Bug: https://github.com/dart-lang/sdk/issues/21965
Change-Id: I81a39df6fd38f9f562b494483b3fe0f67f9a627b
Reviewed-on: https://dart-review.googlesource.com/54360
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2018-05-09 06:44:38 +00:00 committed by commit-bot@chromium.org
parent 964dae472c
commit fd9823ae9e
3 changed files with 261 additions and 0 deletions

View file

@ -98,6 +98,8 @@ class DartFixKind {
'ADD_MISSING_PARAMETER_POSITIONAL',
31,
"Add optional positional parameter");
static const ADD_MISSING_PARAMETER_NAMED = const FixKind(
'ADD_MISSING_PARAMETER_NAMED', 30, "Add named parameter '{0}'");
static const ADD_MISSING_PARAMETER_REQUIRED = const FixKind(
'ADD_MISSING_PARAMETER_REQUIRED', 30, "Add required parameter");
static const ADD_MISSING_REQUIRED_ARGUMENT = const FixKind(

View file

@ -361,6 +361,9 @@ class FixProcessor {
await _addFix_importLibrary_withTopLevelVariable();
await _addFix_createLocalVariable();
}
if (errorCode == StaticWarningCode.UNDEFINED_NAMED_PARAMETER) {
await _addFix_addMissingNamedArgument();
}
if (errorCode == StaticTypeWarningCode.UNDEFINED_METHOD_WITH_CONSTRUCTOR) {
await _addFix_undefinedMethodWithContructor();
}
@ -608,6 +611,60 @@ class FixProcessor {
}
}
Future<Null> _addFix_addMissingNamedArgument() async {
// Prepare the name of the missing parameter.
if (this.node is! SimpleIdentifier) {
return;
}
SimpleIdentifier node = this.node;
String name = node.name;
// We expect that the node is part of a NamedExpression.
if (node.parent?.parent is! NamedExpression) {
return;
}
NamedExpression namedExpression = node.parent.parent;
// We should be in an ArgumentList.
if (namedExpression.parent is! ArgumentList) {
return;
}
AstNode argumentList = namedExpression.parent;
// Prepare the invoked element.
var context =
new _ExecutableParameters(session, astProvider, argumentList.parent);
if (context == null) {
return;
}
// We cannot add named parameters when there are positional positional.
if (context.optionalPositional.isNotEmpty) {
return;
}
Future<void> addParameter(int offset, String prefix, String suffix) async {
if (offset != null) {
DartChangeBuilder changeBuilder = await context.addParameter(
offset, prefix, namedExpression.staticType, name, suffix);
_addFixFromBuilder(
changeBuilder, DartFixKind.ADD_MISSING_PARAMETER_NAMED,
args: [name]);
}
}
if (context.named.isNotEmpty) {
var prevNode = await context.getParameterNode(context.named.last);
await addParameter(prevNode?.end, ', ', '');
} else if (context.required.isNotEmpty) {
var prevNode = await context.getParameterNode(context.required.last);
await addParameter(prevNode?.end, ', {', '}');
} else {
var parameterList = await context.getParameterList();
await addParameter(parameterList?.leftParenthesis?.end, '{', '}');
}
}
Future<Null> _addFix_addMissingParameter() async {
if (node is ArgumentList && node.parent is MethodInvocation) {
ArgumentList argumentList = node;
@ -3729,3 +3786,91 @@ class _ClosestElementFinder {
}
}
}
/**
* [ExecutableElement], its parameters, and operations on them.
*/
class _ExecutableParameters {
final AnalysisSession session;
final AstProvider astProvider;
final ExecutableElement executable;
final List<ParameterElement> required = [];
final List<ParameterElement> optionalPositional = [];
final List<ParameterElement> named = [];
factory _ExecutableParameters(
AnalysisSession session, AstProvider astProvider, AstNode invocation) {
Element element;
if (invocation is InstanceCreationExpression) {
element = invocation.staticElement;
}
if (invocation is MethodInvocation) {
element = invocation.methodName.staticElement;
}
if (element is ExecutableElement) {
return new _ExecutableParameters._(session, astProvider, element);
} else {
return null;
}
}
_ExecutableParameters._(this.session, this.astProvider, this.executable) {
for (var parameter in executable.parameters) {
if (parameter.isNotOptional) {
required.add(parameter);
} else if (parameter.isOptionalPositional) {
optionalPositional.add(parameter);
} else if (parameter.isNamed) {
named.add(parameter);
}
}
}
/**
* Write the code for a new parameter with the given [type] and [name].
*/
Future<DartChangeBuilder> addParameter(int offset, String prefix,
DartType type, String name, String suffix) async {
String targetFile = executable.source.fullName;
var changeBuilder = new DartChangeBuilder(session);
await changeBuilder.addFileEdit(targetFile, (builder) {
builder.addInsertion(offset, (builder) {
builder.write(prefix);
builder.writeParameter(name, type: type);
builder.write(suffix);
});
});
return changeBuilder;
}
/**
* Return the [FormalParameterList] of the [executable], or `null` is cannot
* be found.
*/
Future<FormalParameterList> getParameterList() async {
var name = await astProvider.getParsedNameForElement(executable);
AstNode targetDeclaration = name?.parent;
if (targetDeclaration is FunctionDeclaration) {
FunctionExpression function = targetDeclaration.functionExpression;
return function.parameters;
} else if (targetDeclaration is MethodDeclaration) {
return targetDeclaration.parameters;
}
return null;
}
/**
* Return the [FormalParameter] of the [element] in [FormalParameterList],
* or `null` is cannot be found.
*/
Future<FormalParameter> getParameterNode(ParameterElement element) async {
var name = await astProvider.getParsedNameForElement(element);
for (AstNode node = name; node != null; node = node.parent) {
if (node is FormalParameter && node.parent is FormalParameterList) {
return node;
}
}
return null;
}
}

View file

@ -825,6 +825,120 @@ class A {
''');
}
test_addMissingParameterNamed_function_hasNamed() async {
await resolveTestUnit('''
test(int a, {int b: 0}) {}
main() {
test(1, b: 2, named: 3.0);
}
''');
await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
test(int a, {int b: 0, double named}) {}
main() {
test(1, b: 2, named: 3.0);
}
''');
}
test_addMissingParameterNamed_function_hasRequired() async {
await resolveTestUnit('''
test(int a) {}
main() {
test(1, named: 2.0);
}
''');
await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
test(int a, {double named}) {}
main() {
test(1, named: 2.0);
}
''');
}
test_addMissingParameterNamed_function_noParameters() async {
await resolveTestUnit('''
test() {}
main() {
test(named: 42);
}
''');
await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
test({int named}) {}
main() {
test(named: 42);
}
''');
}
test_addMissingParameterNamed_method_hasNamed() async {
await resolveTestUnit('''
class A {
test(int a, {int b: 0}) {}
main() {
test(1, b: 2, named: 3.0);
}
}
''');
await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
class A {
test(int a, {int b: 0, double named}) {}
main() {
test(1, b: 2, named: 3.0);
}
}
''');
}
test_addMissingParameterNamed_method_hasRequired() async {
await resolveTestUnit('''
class A {
test(int a) {}
main() {
test(1, named: 2.0);
}
}
''');
await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
class A {
test(int a, {double named}) {}
main() {
test(1, named: 2.0);
}
}
''');
}
test_addMissingParameterNamed_method_noParameters() async {
await resolveTestUnit('''
class A {
test() {}
main() {
test(named: 42);
}
}
''');
await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
class A {
test({int named}) {}
main() {
test(named: 42);
}
}
''');
}
test_addMissingRequiredArg_cons_flutter_children() async {
addFlutterPackage();
_addMetaPackageSource();