blob: 729692e6808c90db1ec7f3f5be008cf728a35780 [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/data_driven.dart';
import 'package:analysis_server/src/services/correction/fix/data_driven/change.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
/// The data related to a parameter that has been renamed.
class RenameParameter extends Change<_Data> {
/// The old name of the parameter.
final String oldName;
/// The new name of the parameter.
final String newName;
/// Initialize a newly created transform to describe a renaming of a parameter
/// from the [oldName] to the [newName].
RenameParameter({required this.newName, required this.oldName});
@override
void apply(DartFileEditBuilder builder, DataDrivenFix fix, _Data data) {
if (data is _InvocationData) {
builder.addSimpleReplacement(range.node(data.nameNode), newName);
} else if (data is _OverrideData) {
var declaration = data.methodDeclaration;
var parameter = declaration.parameterNamed(oldName);
if (parameter != null) {
var overriddenMethod = declaration.overriddenElement();
var overriddenParameter = overriddenMethod?.parameterNamed(oldName);
if (overriddenParameter == null) {
// If the overridden parameter has already been removed, then just
// rename the old parameter to have the new name.
var identifier = parameter.identifier;
if (identifier != null) {
builder.addSimpleReplacement(range.node(identifier), newName);
}
} else {
// If the overridden parameter still exists, then mark it as
// deprecated (if it isn't already) and add a declaration of the new
// parameter.
var parameterElement = parameter.declaredElement;
if (parameterElement != null) {
builder.addInsertion(parameter.offset, (builder) {
builder.writeParameter(newName,
isCovariant: parameterElement.isCovariant,
isRequiredNamed: parameterElement.isRequiredNamed,
type: parameterElement.type);
builder.write(', ');
if (!parameterElement.hasDeprecated) {
builder.write('@deprecated ');
}
});
}
}
}
}
}
@override
_Data validate(DataDrivenFix fix) {
var node = fix.node;
if (node is SimpleIdentifier) {
var parent = node.parent;
var grandParent = parent?.parent;
if (node.name == oldName &&
parent is Label &&
grandParent is NamedExpression) {
var invocation = grandParent.parent?.parent;
if (invocation != null && fix.element.matches(invocation)) {
return _InvocationData(node);
}
} else if (parent is MethodDeclaration) {
return _OverrideData(parent);
}
}
return const _IgnoredData();
}
}
/// The data returned from `validate`.
abstract class _Data {
const _Data();
}
/// The data returned when the change doesn't apply.
class _IgnoredData extends _Data {
const _IgnoredData();
}
/// The data returned when updating an invocation site.
class _InvocationData extends _Data {
/// The node representing the name to be replaced.
final SimpleIdentifier nameNode;
/// Initialize newly created data about an invocation site.
_InvocationData(this.nameNode);
}
/// The data returned when updating an override site.
class _OverrideData extends _Data {
/// The node representing the overriding method.
final MethodDeclaration methodDeclaration;
/// Initialize newly created data about an override site.
_OverrideData(this.methodDeclaration);
}
extension on MethodDeclaration {
/// Return the parameter of this method whose name matches the given [name],
/// or `null` if there is no such parameter.
FormalParameter? parameterNamed(String name) {
var parameters = this.parameters;
if (parameters != null) {
for (var parameter in parameters.parameters) {
if (parameter.declaredElement?.name == name) {
return parameter;
}
}
}
return null;
}
/// Return the element that this method overrides, or `null` if this method
/// doesn't override any inherited member.
ExecutableElement? overriddenElement() {
var element = declaredElement;
if (element != null) {
var enclosingElement = element.enclosingElement;
if (enclosingElement is ClassElement) {
var name = Name(enclosingElement.library.source.uri, element.name);
return InheritanceManager3().getInherited2(enclosingElement, name);
}
}
return null;
}
}
extension on ExecutableElement {
/// Return the parameter of this executable element whose name matches the
/// given [name], or `null` if there is no such parameter.
ParameterElement? parameterNamed(String name) {
for (var parameter in parameters) {
if (parameter.name == name) {
return parameter;
}
}
return null;
}
}