Version 2.10.0-42.0.dev
Merge commit '4af6831020f596e44c0ed91da16c47975469dbba' into 'dev'
diff --git a/DEPS b/DEPS
index f16ed79..f6b082a 100644
--- a/DEPS
+++ b/DEPS
@@ -48,7 +48,7 @@
"co19_2_rev": "e48b3090826cf40b8037648f19d211e8eab1b4b6",
# The internal benchmarks to use. See go/dart-benchmarks-internal
- "benchmarks_internal_rev": "01b76c69ca61d3184d4896d230c5bfd439b84d27",
+ "benchmarks_internal_rev": "7030a669aa70e2558cdebb3a89b6d11a34d09051",
"checkout_benchmarks_internal": False,
# As Flutter does, we use Fuchsia's GN and Clang toolchain. These revision
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/modify_parameters.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/modify_parameters.dart
new file mode 100644
index 0000000..c105418
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/modify_parameters.dart
@@ -0,0 +1,350 @@
+// 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:analysis_server/src/services/correction/dart/data_driven.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/change.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/parameter_reference.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+import 'package:meta/meta.dart';
+
+/// The addition of a new parameter.
+class AddParameter extends ParameterModification {
+ /// The index of the parameter in the parameter list after the modifications
+ /// have been applied.
+ final int index;
+
+ /// The name of the parameter that was added.
+ final String name;
+
+ /// A flag indicating whether the parameter is a required parameter.
+ final bool isRequired;
+
+ /// A flag indicating whether the parameter is a positional parameter.
+ final bool isPositional;
+
+ /// The default value of the parameter, or `null` if the parameter doesn't
+ /// have a default value.
+ final ValueExtractor defaultValue;
+
+ /// The value of the new argument in invocations of the function, or `null` if
+ /// the parameter is optional and no argument needs to be added. The only time
+ /// an argument needs to be added for an optional parameter is if the
+ /// parameter is positional and there are pre-existing optional positional
+ /// parameters after the ones being added.
+ final ValueExtractor argumentValue;
+
+ /// Initialize a newly created parameter modification to represent the
+ /// addition of a parameter. If provided, the [argumentValue] will be used as
+ /// the value of the new argument in invocations of the function.
+ AddParameter(this.index, this.name, this.isRequired, this.isPositional,
+ this.defaultValue, this.argumentValue)
+ : assert(index >= 0),
+ assert(name != null);
+}
+
+/// The data related to an executable element whose parameters have been
+/// modified.
+class ModifyParameters extends Change<_Data> {
+ /// A list of the modifications being made.
+ final List<ParameterModification> modifications;
+
+ /// Initialize a newly created transform to modifications to the parameter
+ /// list of a function.
+ ModifyParameters({@required this.modifications})
+ : assert(modifications != null),
+ assert(modifications.isNotEmpty);
+
+ @override
+ void apply(DartFileEditBuilder builder, DataDrivenFix fix, _Data data) {
+ if (data is _InvocationSiteData) {
+ _applyToInvocationSite(builder, fix, data);
+ } else if (data is _OverrideData) {
+ _applyToOverride(builder, fix, data);
+ } else {
+ throw StateError('Unsupported class of data: ${data.runtimeType}');
+ }
+ }
+
+ @override
+ _Data validate(DataDrivenFix fix) {
+ var node = fix.node;
+ var parent = node.parent;
+ if (parent is InvocationExpression) {
+ var argumentList = parent.argumentList;
+ return _InvocationSiteData(argumentList);
+ }
+ // TODO(brianwilkerson) Recognize cases where a method that used to be an
+ // override of a removed method needs to be migrated and return an
+ // [_OverrideData] to represent that case.
+ return null;
+ }
+
+ void _applyToInvocationSite(DartFileEditBuilder builder, DataDrivenFix fix,
+ _InvocationSiteData data) {
+ var argumentList = data.argumentList;
+ var arguments = argumentList.arguments;
+ var argumentCount = arguments.length;
+ var newNamed = <AddParameter>[];
+ var indexToNewArgumentMap = <int, AddParameter>{};
+ var argumentsToInsert = <int>[];
+ var argumentsToDelete = <int>[];
+ var remainingArguments = [for (var i = 0; i < argumentCount; i++) i];
+ for (var modification in modifications) {
+ if (modification is AddParameter) {
+ var index = modification.index;
+ indexToNewArgumentMap[index] = modification;
+ if (modification.isPositional) {
+ argumentsToInsert.add(index);
+ } else if (modification.isRequired) {
+ newNamed.add(modification);
+ }
+ } else if (modification is RemoveParameter) {
+ var argument = modification.parameter.argumentFrom(argumentList);
+ // If there is no argument corresponding to the parameter then we assume
+ // that the parameter was optional (and absent) and don't try to remove
+ // it.
+ if (argument != null) {
+ var index = arguments.indexOf(_realArgument(argument));
+ argumentsToDelete.add(index);
+ remainingArguments.remove(index);
+ }
+ }
+ }
+ argumentsToInsert.sort();
+ newNamed.sort((first, second) => first.name.compareTo(second.name));
+
+ /// Write to the [builder] the argument associated with a single
+ /// [parameter].
+ void writeArgument(DartEditBuilder builder, AddParameter parameter) {
+ var value = parameter.argumentValue.from(argumentList, fix.utils);
+ if (!parameter.isPositional) {
+ builder.write(parameter.name);
+ builder.write(': ');
+ }
+ builder.write(value);
+ }
+
+ var insertionRanges = argumentsToInsert.contiguousSubRanges.toList();
+ var deletionRanges = argumentsToDelete.contiguousSubRanges.toList();
+ if (insertionRanges.isNotEmpty) {
+ /// Write to the [builder] the new arguments in the [insertionRange]. If
+ /// [needsInitialComma] is `true` then we need to write a comma before the
+ /// first of the new arguments.
+ void writeInsertionRange(DartEditBuilder builder,
+ _IndexRange insertionRange, bool needsInitialComma) {
+ var needsComma = needsInitialComma;
+ for (var argumentIndex = insertionRange.lower;
+ argumentIndex <= insertionRange.upper;
+ argumentIndex++) {
+ if (needsComma) {
+ builder.write(', ');
+ } else {
+ needsComma = true;
+ }
+ var parameter = indexToNewArgumentMap[argumentIndex];
+ writeArgument(builder, parameter);
+ }
+ }
+
+ var nextRemaining = 0;
+ var nextInsertionRange = 0;
+ var insertionCount = 0;
+ while (nextRemaining < remainingArguments.length &&
+ nextInsertionRange < insertionRanges.length) {
+ var remainingIndex = remainingArguments[nextRemaining];
+ var insertionRange = insertionRanges[nextInsertionRange];
+ var insertionIndex = insertionRange.lower;
+ if (insertionIndex <= remainingIndex + insertionCount) {
+ // There are arguments that need to be inserted before the next
+ // remaining argument.
+ var deletionRange =
+ _rangeContaining(deletionRanges, insertionIndex - 1);
+ if (deletionRange == null) {
+ // The insertion range doesn't overlap a deletion range, so insert
+ // the added arguments before the argument whose index is
+ // `remainingIndex`.
+ int offset;
+ var needsInitialComma = false;
+ if (insertionIndex > 0) {
+ offset = arguments[remainingIndex - 1].end;
+ needsInitialComma = true;
+ } else {
+ offset = arguments[remainingIndex].offset;
+ }
+ builder.addInsertion(offset, (builder) {
+ writeInsertionRange(builder, insertionRange, needsInitialComma);
+ if (insertionIndex == 0) {
+ builder.write(', ');
+ }
+ });
+ } else {
+ // The insertion range overlaps a deletion range, so replace the
+ // arguments in the deletion range with the arguments in the
+ // insertion range.
+ var replacementRange = range.argumentRange(
+ argumentList, deletionRange.lower, deletionRange.upper, false);
+ builder.addReplacement(replacementRange, (builder) {
+ writeInsertionRange(builder, insertionRange, false);
+ });
+ deletionRanges.remove(deletionRange);
+ }
+ insertionCount += insertionRange.count;
+ nextInsertionRange++;
+ } else {
+ // There are no arguments that need to be inserted before the next
+ // remaining argument, so just move past the next remaining argument.
+ nextRemaining++;
+ }
+ }
+ // The remaining insertion ranges might include new required arguments
+ // that need to be inserted after the last argument.
+ var offset = arguments[arguments.length - 1].end;
+ while (nextInsertionRange < insertionRanges.length) {
+ var insertionRange = insertionRanges[nextInsertionRange];
+ var lower = insertionRange.lower;
+ var upper = insertionRange.upper;
+ while (upper >= lower && !indexToNewArgumentMap[upper].isRequired) {
+ upper--;
+ }
+ if (upper >= lower) {
+ builder.addInsertion(offset, (builder) {
+ writeInsertionRange(builder, _IndexRange(lower, upper), true);
+ });
+ }
+ nextInsertionRange++;
+ }
+ }
+ //
+ // Insert arguments for required named parameters.
+ //
+ if (newNamed.isNotEmpty) {
+ int offset;
+ var needsInitialComma = false;
+ if (arguments.isEmpty) {
+ offset = argumentList.rightParenthesis.offset;
+ } else {
+ offset = arguments[arguments.length - 1].end;
+ needsInitialComma = true;
+ }
+ builder.addInsertion(offset, (builder) {
+ for (var i = 0; i < newNamed.length; i++) {
+ if (i > 0 || needsInitialComma) {
+ builder.write(', ');
+ }
+ writeArgument(builder, newNamed[i]);
+ }
+ });
+ }
+ //
+ // The remaining deletion ranges are now ready to be removed.
+ //
+ for (var subRange in deletionRanges) {
+ builder.addDeletion(range.argumentRange(
+ argumentList, subRange.lower, subRange.upper, true));
+ }
+ }
+
+ void _applyToOverride(
+ DartFileEditBuilder builder, DataDrivenFix fix, _OverrideData data) {
+ // TODO(brianwilkerson) Implement this.
+ throw UnsupportedError('Updating override sites is not yet supported.');
+ }
+
+ /// Return the range from the list of [ranges] that contains the given
+ /// [index], or `null` if there is no such range.
+ _IndexRange _rangeContaining(List<_IndexRange> ranges, int index) {
+ for (var range in ranges) {
+ if (index >= range.lower && index <= range.upper) {
+ return range;
+ }
+ }
+ return null;
+ }
+
+ /// Return the element of the argument list whose value is the given
+ /// [argument]. If the argument is the child of a named expression, then that
+ /// will be the named expression, otherwise it will be the argument itself.
+ Expression _realArgument(Expression argument) =>
+ argument.parent is NamedExpression ? argument.parent : argument;
+}
+
+/// A modification related to a parameter.
+abstract class ParameterModification {}
+
+/// The removal of an existing parameter.
+class RemoveParameter extends ParameterModification {
+ /// The parameter that was removed.
+ final ParameterReference parameter;
+
+ /// Initialize a newly created parameter modification to represent the removal
+ /// of an existing [parameter].
+ RemoveParameter(this.parameter) : assert(parameter != null);
+}
+
+/// The data returned when modifying a parameter list.
+class _Data {}
+
+/// A range of indexes within a list.
+class _IndexRange {
+ /// The index of the first element in the range.
+ final int lower;
+
+ /// The index of the last element in the range. This will be the same as the
+ /// [lower] if there is a single element in the range.
+ final int upper;
+
+ /// Initialize a newly created range.
+ _IndexRange(this.lower, this.upper);
+
+ /// Return the number of indices in this range.
+ int get count => upper - lower + 1;
+
+ @override
+ String toString() => '[$lower..$upper]';
+}
+
+/// The data returned when updating an invocation site.
+class _InvocationSiteData extends _Data {
+ /// The argument list to be updated.
+ final ArgumentList argumentList;
+
+ /// Initialize a newly created data object with the data needed to update an
+ /// invocation site.
+ _InvocationSiteData(this.argumentList);
+}
+
+/// The data returned when updating an override of the modified method.
+class _OverrideData extends _Data {
+ /// The parameter list to be updated.
+ final FormalParameterList parameter;
+
+ /// Initialize a newly created data object with the data needed to update an
+ /// override of the modified method.
+ _OverrideData(this.parameter);
+}
+
+extension on List<int> {
+ Iterable<_IndexRange> get contiguousSubRanges sync* {
+ if (isEmpty) {
+ return;
+ }
+ var lower = this[0];
+ var previous = lower;
+ var index = 1;
+ while (index < length) {
+ var current = this[index];
+ if (current == previous + 1) {
+ previous = current;
+ } else {
+ yield _IndexRange(lower, previous);
+ lower = previous = current;
+ }
+ index++;
+ }
+ yield _IndexRange(lower, previous);
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart
new file mode 100644
index 0000000..9ad4435
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart
@@ -0,0 +1,849 @@
+// 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:analysis_server/src/services/correction/fix/data_driven/element_descriptor.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/modify_parameters.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/parameter_reference.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/rename_change.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/transform.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'data_driven_test_support.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(ModifyParameters_DeprecatedMemberUseTest);
+ });
+}
+
+/// In the tests where a required named parameter is being added, the tests
+/// avoid the question of whether the defining library is opted in to the
+/// null-safety feature by omitting both the `required` keyword and annotation.
+/// This works because the information the change needs is taken from the
+/// `AddParameter` object rather than the source code.
+///
+/// The tests for 'function' exist to check that these changes can also be
+/// applied to top-level functions, but are not intended to be exhaustive.
+@reflectiveTest
+class ModifyParameters_DeprecatedMemberUseTest extends _ModifyParameters {
+ Future<void> test_add_function_first_optionalNamed() async {
+ setPackageContent('''
+@deprecated
+void f(int b) {}
+void g(int a, int b) {}
+''');
+ setPackageData(_modify([
+ 'f'
+ ], [
+ AddParameter(0, 'a', true, true, null, LiteralExtractor('0')),
+ ], newName: 'g'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void h() {
+ f(1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void h() {
+ g(0, 1);
+}
+''');
+ }
+
+ Future<void> test_add_method_first_optionalNamed() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m({int b}) {}
+ void m2({int a, int b}) {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [AddParameter(1, 'a', false, false, null, null)],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(b: 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(b: 1);
+}
+''');
+ }
+
+ Future<void> test_add_method_first_optionalPositional() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m([int b]) {}
+ void m2([int a, int b]) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ AddParameter(0, 'a', false, true, null, LiteralExtractor('0'))
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0, 1);
+}
+''');
+ }
+
+ Future<void> test_add_method_first_requiredNamed() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m({int b}) {}
+ void m2({int a, int b}) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ AddParameter(0, 'a', true, false, null, LiteralExtractor('0'))
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(b: 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(b: 1, a: 0);
+}
+''');
+ }
+
+ Future<void> test_add_method_first_requiredPositional() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int b) {}
+ void m2(int a, int b) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ AddParameter(0, 'a', true, true, null, LiteralExtractor('0'))
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0, 1);
+}
+''');
+ }
+
+ Future<void> test_add_method_last_optionalNamed() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a) {}
+ void m2(int a, {int b}) {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [AddParameter(1, 'b', false, false, null, null)],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0);
+}
+''');
+ }
+
+ Future<void> test_add_method_last_optionalPositional() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a) {}
+ void m2(int a, [int b]) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ AddParameter(1, 'b', false, true, null, LiteralExtractor('1'))
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0);
+}
+''');
+ }
+
+ Future<void> test_add_method_last_requiredNamed() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a) {}
+ void m2(int a, {int b}) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ AddParameter(1, 'b', true, false, null, LiteralExtractor('1'))
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0, b: 1);
+}
+''');
+ }
+
+ Future<void> test_add_method_last_requiredPositional() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a) {}
+ void m2(int a, int b) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ AddParameter(1, 'b', true, true, null, LiteralExtractor('1'))
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0, 1);
+}
+''');
+ }
+
+ Future<void> test_add_method_multiple() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a, int d, int f) {}
+ void m2(int a, int b, int c, int d, int e, int f) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ AddParameter(1, 'b', true, true, null, LiteralExtractor('1')),
+ AddParameter(2, 'c', true, true, null, LiteralExtractor('2')),
+ AddParameter(4, 'e', true, true, null, LiteralExtractor('4')),
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0, 3, 5);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0, 1, 2, 3, 4, 5);
+}
+''');
+ }
+
+ Future<void> test_mixed_method_noOverlap_removedFirst() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a, int b) {}
+ void m2(int b, int c) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ RemoveParameter(PositionalParameterReference(0)),
+ AddParameter(2, 'c', true, true, null, LiteralExtractor('2'))
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0, 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(1, 2);
+}
+''');
+ }
+
+ Future<void> test_mixed_method_noOverlap_removedLast() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int b, int c) {}
+ void m2(int a, int b) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ RemoveParameter(PositionalParameterReference(1)),
+ AddParameter(0, 'a', true, true, null, LiteralExtractor('0'))
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(1, 2);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0, 1);
+}
+''');
+ }
+
+ Future<void> test_mixed_method_overlap_first() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m1(int a, int b, int d) {}
+ void m2( int c, int d) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm1'
+ ], [
+ RemoveParameter(PositionalParameterReference(0)),
+ RemoveParameter(PositionalParameterReference(1)),
+ AddParameter(0, 'c', true, true, null, LiteralExtractor('2')),
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m1(0, 1, 3);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(2, 3);
+}
+''');
+ }
+
+ Future<void> test_mixed_method_overlap_last() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m1(int a, int b, int c) {}
+ void m2(int a, int d) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm1'
+ ], [
+ RemoveParameter(PositionalParameterReference(1)),
+ RemoveParameter(PositionalParameterReference(2)),
+ AddParameter(1, 'd', true, true, null, LiteralExtractor('3')),
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m1(0, 1, 2);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0, 3);
+}
+''');
+ }
+
+ Future<void> test_mixed_method_overlap_middle() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m1( int b, int c, int e, int f, int g) {}
+ void m2(int a, int b, int d, int e, int g) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm1'
+ ], [
+ AddParameter(0, 'a', true, true, null, LiteralExtractor('0')),
+ RemoveParameter(PositionalParameterReference(1)),
+ RemoveParameter(PositionalParameterReference(3)),
+ AddParameter(2, 'd', true, true, null, LiteralExtractor('3')),
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m1(1, 2, 4, 5, 6);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0, 1, 3, 4, 6);
+}
+''');
+ }
+
+ Future<void> test_remove_function_first_requiredPositional() async {
+ setPackageContent('''
+@deprecated
+void f(int a, int b) {}
+void g(int b) {}
+''');
+ setPackageData(_modify(
+ ['f'], [RemoveParameter(PositionalParameterReference(0))],
+ newName: 'g'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void h() {
+ f(0, 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void h() {
+ g(1);
+}
+''');
+ }
+
+ Future<void> test_remove_method_first_optionalNamed() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m({int a, int b}) {}
+ void m2({int b}) {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(NamedParameterReference('a'))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(a: 0, b: 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(b: 1);
+}
+''');
+ }
+
+ Future<void> test_remove_method_first_optionalPositional() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m([int a, int b]) {}
+ void m2([int b]) {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(PositionalParameterReference(0))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0, 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(1);
+}
+''');
+ }
+
+ Future<void> test_remove_method_first_requiredPositional() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a, int b) {}
+ void m2(int b) {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(PositionalParameterReference(0))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0, 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(1);
+}
+''');
+ }
+
+ Future<void> test_remove_method_last_optionalNamed() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m({int a, int b}) {}
+ void m2({int b}) {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(NamedParameterReference('b'))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(a: 0, b: 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(a: 0);
+}
+''');
+ }
+
+ Future<void> test_remove_method_last_optionalPositional() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m([int a, int b]) {}
+ void m2([int b]) {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(PositionalParameterReference(1))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0, 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0);
+}
+''');
+ }
+
+ Future<void> test_remove_method_last_requiredPositional() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a, int b) {}
+ void m2(int b) {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(PositionalParameterReference(1))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0, 1);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(0);
+}
+''');
+ }
+
+ Future<void> test_remove_method_multiple() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a, int b, int c, int d) {}
+ void m2(int b) {}
+}
+''');
+ setPackageData(_modify([
+ 'C',
+ 'm'
+ ], [
+ RemoveParameter(PositionalParameterReference(0)),
+ RemoveParameter(PositionalParameterReference(2)),
+ RemoveParameter(PositionalParameterReference(3)),
+ ], newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0, 1, 2, 3);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2(1);
+}
+''');
+ }
+
+ Future<void> test_remove_method_only_optionalNamed_withArg() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m({int a}) {}
+ void m2() {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(NamedParameterReference('a'))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(a: 0);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2();
+}
+''');
+ }
+
+ Future<void> test_remove_method_only_optionalNamed_withoutArg() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m({int a}) {}
+ void m2() {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(NamedParameterReference('a'))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2();
+}
+''');
+ }
+
+ Future<void> test_remove_method_only_optionalPositional_withArg() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m([int a]) {}
+ void m2() {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(PositionalParameterReference(0))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2();
+}
+''');
+ }
+
+ Future<void> test_remove_method_only_optionalPositional_withoutArg() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m([int a]) {}
+ void m2() {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(PositionalParameterReference(0))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2();
+}
+''');
+ }
+
+ Future<void> test_remove_method_only_requiredPositional() async {
+ setPackageContent('''
+class C {
+ @deprecated
+ void m(int a) {}
+ void m2() {}
+}
+''');
+ setPackageData(_modify(
+ ['C', 'm'], [RemoveParameter(PositionalParameterReference(0))],
+ newName: 'm2'));
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+ c.m(0);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+ c.m2();
+}
+''');
+ }
+}
+
+abstract class _ModifyParameters extends DataDrivenFixProcessorTest {
+ Transform _modify(List<String> originalComponents,
+ List<ParameterModification> modifications, {String newName}) =>
+ Transform(
+ title: 'title',
+ element: ElementDescriptor(
+ libraryUris: [importUri], components: originalComponents),
+ changes: [
+ ModifyParameters(modifications: modifications),
+ if (newName != null) RenameChange(newName: newName),
+ ]);
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
index dd6eaea..8773441 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
@@ -5,11 +5,13 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'add_type_parameter_change_test.dart' as add_type_parameter_change;
+import 'modify_parameters_test.dart' as modify_parameters;
import 'rename_change_test.dart' as rename_change;
void main() {
defineReflectiveSuite(() {
add_type_parameter_change.main();
+ modify_parameters.main();
rename_change.main();
});
}
diff --git a/pkg/analyzer_plugin/lib/utilities/range_factory.dart b/pkg/analyzer_plugin/lib/utilities/range_factory.dart
index c791818..fbcbaa3 100644
--- a/pkg/analyzer_plugin/lib/utilities/range_factory.dart
+++ b/pkg/analyzer_plugin/lib/utilities/range_factory.dart
@@ -15,6 +15,51 @@
/// A factory used to create instances of [SourceRange] based on various
/// syntactic and semantic entities.
class RangeFactory {
+ /// Return a source range that covers all of the arguments in the
+ /// [argumentList] between the [lower] and [upper] indices, inclusive. The
+ /// flag [forDeletion] controls whether a comma between the given indices and
+ /// the neighboring arguments should be included in the range. If the flag is
+ /// `true`, then the range can be deleted to delete the covered arguments and
+ /// leave a valid argument list. If the flag is `false`, then the range can be
+ /// replaced with different argument values.
+ ///
+ /// For example, given an argument list of `(a, b, c, d)`, a lower index of
+ /// `1` and an upper index of `2`, the range will cover the text `'b, c'` if
+ /// [forDeletion] is `false` and the text `', b, c'` if [forDeletion] is
+ /// `true`.
+ ///
+ /// Throws and exception if either the [lower] or [upper] bound is not a valid
+ /// index into the [argumentList] or if the [upper] bound is less than the
+ /// [lower] bound.
+ SourceRange argumentRange(
+ ArgumentList argumentList, int lower, int upper, bool forDeletion) {
+ var arguments = argumentList.arguments;
+ assert(lower >= 0 && lower < arguments.length);
+ assert(upper >= lower && upper < arguments.length);
+ if (lower == upper) {
+ // Remove a single argument.
+ if (forDeletion) {
+ return nodeInList(arguments, arguments[lower]);
+ }
+ return node(arguments[lower]);
+ } else if (!forDeletion) {
+ return startEnd(arguments[lower], arguments[upper]);
+ } else if (lower == 0) {
+ if (upper == arguments.length - 1) {
+ // Remove all of the arguments.
+ return endStart(
+ argumentList.leftParenthesis, argumentList.rightParenthesis);
+ } else {
+ // Remove a subset of the arguments starting with the first argument.
+ return startStart(arguments[lower], arguments[upper + 1]);
+ }
+ } else {
+ // Remove a subset of the arguments starting in the middle of the
+ // arguments.
+ return endEnd(arguments[lower - 1], arguments[upper]);
+ }
+ }
+
/// Return a source range that covers the name of the given [element].
SourceRange elementName(Element element) {
return SourceRange(element.nameOffset, element.nameLength);
diff --git a/pkg/analyzer_plugin/test/utilities/range_factory_test.dart b/pkg/analyzer_plugin/test/utilities/range_factory_test.dart
index 9ab8590..a2ac201 100644
--- a/pkg/analyzer_plugin/test/utilities/range_factory_test.dart
+++ b/pkg/analyzer_plugin/test/utilities/range_factory_test.dart
@@ -31,6 +31,146 @@
return invocation.argumentList.arguments;
}
+ Future<void> test_argumentRange_all_mixed_noTrailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1, c: 2);
+}
+void g(int a, int b, {int c}) {}
+''');
+ _assertArgumentRange(0, 2, SourceRange(15, 10), SourceRange(15, 10));
+ }
+
+ Future<void> test_argumentRange_all_mixed_trailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1, c: 2, );
+}
+void g(int a, int b, {int c}) {}
+''');
+ _assertArgumentRange(0, 2, SourceRange(15, 12), SourceRange(15, 10));
+ }
+
+ Future<void> test_argumentRange_all_named_noTrailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(a: 0, b: 1, c: 2);
+}
+void g({int a, int b, int c}) {}
+''');
+ _assertArgumentRange(0, 2, SourceRange(15, 16), SourceRange(15, 16));
+ }
+
+ Future<void> test_argumentRange_all_named_trailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(a: 0, b: 1, c: 2, );
+}
+void g({int a, int b, int c}) {}
+''');
+ _assertArgumentRange(0, 2, SourceRange(15, 18), SourceRange(15, 16));
+ }
+
+ Future<void> test_argumentRange_all_positional_noTrailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1, 2);
+}
+void g(int a, int b, int c) {}
+''');
+ _assertArgumentRange(0, 2, SourceRange(15, 7), SourceRange(15, 7));
+ }
+
+ Future<void> test_argumentRange_all_positional_trailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1, 2, );
+}
+void g(int a, int b, int c) {}
+''');
+ _assertArgumentRange(0, 2, SourceRange(15, 9), SourceRange(15, 7));
+ }
+
+ Future<void> test_argumentRange_first_noTrailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1);
+}
+void g(int a, int b) {}
+''');
+ _assertArgumentRange(0, 0, SourceRange(15, 3), SourceRange(15, 1));
+ }
+
+ Future<void> test_argumentRange_first_trailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1, );
+}
+void g(int a, int b) {}
+''');
+ _assertArgumentRange(0, 0, SourceRange(15, 3), SourceRange(15, 1));
+ }
+
+ Future<void> test_argumentRange_last_noTrailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1);
+}
+void g(int a, int b) {}
+''');
+ _assertArgumentRange(1, 1, SourceRange(16, 3), SourceRange(18, 1));
+ }
+
+ Future<void> test_argumentRange_last_trailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1, );
+}
+void g(int a, int b) {}
+''');
+ _assertArgumentRange(1, 1, SourceRange(16, 3), SourceRange(18, 1));
+ }
+
+ Future<void> test_argumentRange_middle_noTrailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1, 2, 3);
+}
+void g(int a, int b, int c, int d) {}
+''');
+ _assertArgumentRange(1, 2, SourceRange(16, 6), SourceRange(18, 4));
+ }
+
+ Future<void> test_argumentRange_middle_trailingComma() async {
+ await resolveTestUnit('''
+void f() {
+ g(0, 1, 2, 3, );
+}
+void g(int a, int b, int c, int d) {}
+''');
+ _assertArgumentRange(1, 2, SourceRange(16, 6), SourceRange(18, 4));
+ }
+
+ Future<void> test_argumentRange_only_named() async {
+ await resolveTestUnit('''
+void f() {
+ g(a: 0);
+}
+void g({int a}) {}
+''');
+ _assertArgumentRange(0, 0, SourceRange(15, 4), SourceRange(15, 4));
+ }
+
+ Future<void> test_argumentRange_only_positional() async {
+ await resolveTestUnit('''
+void f() {
+ g(0);
+}
+void g(int a) {}
+''');
+ _assertArgumentRange(0, 0, SourceRange(15, 1), SourceRange(15, 1));
+ }
+
Future<void> test_elementName() async {
await resolveTestUnit('class ABC {}');
var element = findElement('ABC');
@@ -233,4 +373,22 @@
var mainName = mainFunction.name;
expect(range.token(mainName.beginToken), SourceRange(1, 4));
}
+
+ /// Assuming that the test code starts with a function whose block body starts
+ /// with a method invocation, compute the range for the arguments in the
+ /// invocation's argument list between [lower] and [upper]. Validate that the
+ /// range for deletion matches [expectedForDeletion] and that the range not
+ /// for deletion matches [expectedNoDeletion].
+ void _assertArgumentRange(int lower, int upper,
+ SourceRange expectedForDeletion, SourceRange expectedNoDeletion) {
+ var f = testUnit.declarations[0] as FunctionDeclaration;
+ var body = f.functionExpression.body as BlockFunctionBody;
+ var statement = body.block.statements[0] as ExpressionStatement;
+ var invocation = statement.expression as MethodInvocation;
+ var argumentList = invocation.argumentList;
+ expect(range.argumentRange(argumentList, lower, upper, true),
+ expectedForDeletion);
+ expect(range.argumentRange(argumentList, lower, upper, false),
+ expectedNoDeletion);
+ }
}
diff --git a/tools/VERSION b/tools/VERSION
index 686f446..433cea6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 10
PATCH 0
-PRERELEASE 41
+PRERELEASE 42
PRERELEASE_PATCH 0
\ No newline at end of file