blob: 34f81c77692aa45bfd8bba852a9cfc0602735756 [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:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/executable_parameters.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/levenshtein.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
class ChangeArgumentName extends MultiCorrectionProducer {
/// The maximum Levenshtein distance between the existing name and a possible
/// replacement before the replacement is deemed to not be worth offering.
static const _maxDistance = 4;
@override
Iterable<CorrectionProducer> get producers sync* {
var names = _getNamedParameterNames();
if (names == null || names.isEmpty) {
return;
}
SimpleIdentifier argumentName = node;
var invalidName = argumentName.name;
for (var proposedName in names) {
var distance = _computeDistance(invalidName, proposedName);
if (distance <= _maxDistance) {
// TODO(brianwilkerson) Create a way to use the distance as part of the
// computation of the priority (so that closer names sort first).
yield _ChangeName(argumentName, proposedName);
}
}
}
int _computeDistance(String current, String proposal) {
if ((current == 'child' && proposal == 'children') ||
(current == 'children' && proposal == 'child')) {
// Special case handling for 'child' and 'children' is unnecessary if
// `maxDistance >= 3`, but is included to prevent regression in case the
// value is changed to improve results.
return 1;
}
return levenshtein(current, proposal, _maxDistance, caseSensitive: false);
}
List<String> _getNamedParameterNames() {
var namedExpression = node?.parent?.parent;
if (node is SimpleIdentifier &&
namedExpression is NamedExpression &&
namedExpression.name == node.parent &&
namedExpression.parent is ArgumentList) {
var parameters = ExecutableParameters(
sessionHelper,
namedExpression.parent.parent,
);
return parameters?.namedNames;
}
return null;
}
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static ChangeArgumentName newInstance() => ChangeArgumentName();
}
/// A correction processor that can make one of the possible change computed by
/// the [ChangeArgumentName] producer.
class _ChangeName extends CorrectionProducer {
/// The name of the argument being changed.
final SimpleIdentifier _argumentName;
/// The name to which the argument name will be changed.
final String _proposedName;
_ChangeName(this._argumentName, this._proposedName);
@override
List<Object> get fixArguments => [_proposedName];
@override
FixKind get fixKind => DartFixKind.CHANGE_ARGUMENT_NAME;
@override
Future<void> compute(ChangeBuilder builder) async {
await builder.addDartFileEdit(file, (builder) {
builder.addSimpleReplacement(range.node(_argumentName), _proposedName);
});
}
}