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>
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 3ba57d8..cdcdc36 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -98,6 +98,8 @@
'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(
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 5453c62..79b6f55 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -361,6 +361,9 @@
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 @@
}
}
+ 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 @@
}
}
}
+
+/**
+ * [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;
+ }
+}
diff --git a/pkg/analysis_server/test/services/correction/fix_test.dart b/pkg/analysis_server/test/services/correction/fix_test.dart
index a95b938..0f0bb9c 100644
--- a/pkg/analysis_server/test/services/correction/fix_test.dart
+++ b/pkg/analysis_server/test/services/correction/fix_test.dart
@@ -825,6 +825,120 @@
''');
}
+ 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();