blob: dd3981831a8940cbf79d5f407fe07fb062ba4916 [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/fix.dart';
import 'package:analysis_server/src/services/correction/util.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' show Position;
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 CreateMethod extends CorrectionProducer {
/// The kind of method to be created.
final _MethodKind _kind;
String _memberName;
CreateMethod(this._kind);
@override
List<Object> get fixArguments => [_memberName];
@override
FixKind get fixKind => DartFixKind.CREATE_METHOD;
@override
Future<void> compute(ChangeBuilder builder) async {
if (_kind == _MethodKind.equalsOrHashCode) {
await createEqualsOrHashCode(builder);
} else if (_kind == _MethodKind.method) {
await createMethod(builder);
}
}
Future<void> createEqualsOrHashCode(ChangeBuilder builder) async {
final memberDecl = node.thisOrAncestorOfType<ClassMember>();
if (memberDecl == null) {
return;
}
final classDecl = memberDecl.thisOrAncestorOfType<ClassDeclaration>();
if (classDecl != null) {
final classElement = classDecl.declaredElement;
var missingEquals = memberDecl is FieldDeclaration ||
(memberDecl as MethodDeclaration).name.name == 'hashCode';
ExecutableElement element;
if (missingEquals) {
_memberName = '==';
element = classElement.lookUpInheritedMethod(
_memberName, classElement.library);
} else {
_memberName = 'hashCode';
element = classElement.lookUpInheritedConcreteGetter(
_memberName, classElement.library);
}
final location =
utils.prepareNewClassMemberLocation(classDecl, (_) => true);
await builder.addDartFileEdit(file, (fileBuilder) {
fileBuilder.addInsertion(location.offset, (builder) {
builder.write(location.prefix);
builder.writeOverride(element, invokeSuper: true);
builder.write(location.suffix);
});
});
builder.setSelection(Position(file, location.offset));
}
}
Future<void> createMethod(ChangeBuilder builder) async {
if (node is! SimpleIdentifier || node.parent is! MethodInvocation) {
return;
}
_memberName = (node as SimpleIdentifier).name;
var invocation = node.parent as MethodInvocation;
// prepare environment
Element targetElement;
var staticModifier = false;
CompilationUnitMember targetNode;
var target = invocation.realTarget;
var utils = this.utils;
if (target is ExtensionOverride) {
targetElement = target.staticElement;
targetNode = await getExtensionDeclaration(targetElement);
if (targetNode == null) {
return;
}
} else if (target is Identifier &&
target.staticElement is ExtensionElement) {
targetElement = target.staticElement;
targetNode = await getExtensionDeclaration(targetElement);
if (targetNode == null) {
return;
}
staticModifier = true;
} else if (target == null) {
targetElement = unit.declaredElement;
var enclosingMember = node.thisOrAncestorOfType<ClassMember>();
if (enclosingMember == null) {
// If the undefined identifier isn't inside a class member, then it
// doesn't make sense to create a method.
return;
}
targetNode = enclosingMember.parent;
staticModifier = inStaticContext;
} else {
var targetClassElement = getTargetClassElement(target);
if (targetClassElement == null) {
return;
}
targetElement = targetClassElement;
if (targetClassElement.librarySource.isInSystemLibrary) {
return;
}
// prepare target ClassDeclaration
targetNode = await getClassOrMixinDeclaration(targetClassElement);
if (targetNode == null) {
return;
}
// maybe static
if (target is Identifier) {
staticModifier = target.staticElement.kind == ElementKind.CLASS;
}
// use different utils
var targetPath = targetClassElement.source.fullName;
var targetResolveResult =
await resolvedResult.session.getResolvedUnit(targetPath);
utils = CorrectionUtils(targetResolveResult);
}
var targetLocation = utils.prepareNewMethodLocation(targetNode);
var targetFile = targetElement.source.fullName;
// build method source
await builder.addDartFileEdit(targetFile, (builder) {
builder.addInsertion(targetLocation.offset, (builder) {
builder.write(targetLocation.prefix);
// maybe "static"
if (staticModifier) {
builder.write('static ');
}
// append return type
{
var type = inferUndefinedExpressionType(invocation);
if (builder.writeType(type, groupName: 'RETURN_TYPE')) {
builder.write(' ');
}
}
// append name
builder.addLinkedEdit('NAME', (builder) {
builder.write(_memberName);
});
builder.write('(');
builder.writeParametersMatchingArguments(invocation.argumentList);
builder.write(') {}');
builder.write(targetLocation.suffix);
});
if (targetFile == file) {
builder.addLinkedPosition(range.node(node), 'NAME');
}
});
}
/// Return an instance of this class that will create either an equals
/// (operator =) or `hashCode` method based on the existing other half of the
/// pair. Used as a tear-off in `FixProcessor`.
static CreateMethod equalsOrHashCode() =>
CreateMethod(_MethodKind.equalsOrHashCode);
/// Return an instance of this class that will create a method based on an
/// invocation of an undefined method. Used as a tear-off in `FixProcessor`.
static CreateMethod method() => CreateMethod(_MethodKind.method);
}
/// A representation of the kind of element that should be suggested.
enum _MethodKind {
equalsOrHashCode,
method,
}