blob: 6fbcad413c417c06d67adecb7a13d72bf8a5e775 [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:analysis_server/src/services/correction/fix/data_driven/code_template.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:meta/meta.dart';
/// The data related to a type parameter that was added to either a function or
/// a type.
class AddTypeParameter extends Change<_Data> {
/// The index of the type parameter that was added.
final int index;
/// The name of the type parameter that was added.
final String name;
/// The name of the type that the type parameter extends, or `null` if the
/// type parameter doesn't have a bound.
final CodeTemplate extendedType;
/// The code template used to compute the value of the type argument.
final CodeTemplate argumentValue;
/// Initialize a newly created change to describe adding a type parameter to a
/// type or a function.
AddTypeParameter(
{@required this.index,
@required this.name,
@required this.argumentValue,
@required this.extendedType})
: assert(index >= 0),
assert(name != null),
assert(argumentValue != null);
@override
void apply(DartFileEditBuilder builder, DataDrivenFix fix, _Data data) {
if (data is _TypeArgumentData) {
_applyToTypeArguments(builder, fix, data);
} else if (data is _TypeParameterData) {
_applyToTypeParameters(builder, fix, data);
} else {
throw StateError('Unsupported class of data: ${data.runtimeType}');
}
}
@override
_Data validate(DataDrivenFix fix) {
var node = fix.node;
var context = TemplateContext(getInvocation(node), fix.utils);
if (node is NamedType) {
// wrong_number_of_type_arguments
// wrong_number_of_type_arguments_constructor
if (!argumentValue.validate(context)) {
return null;
}
var typeArguments = node.typeArguments;
if (_isInvalidIndex(typeArguments?.arguments)) {
return null;
}
return _TypeArgumentData(typeArguments, node.name.end);
}
var parent = node.parent;
if (parent is InvocationExpression) {
// wrong_number_of_type_arguments_method
var argument = argumentValue.validate(context);
if (argument == null) {
return null;
}
var typeArguments = parent.typeArguments;
if (_isInvalidIndex(typeArguments?.arguments)) {
return null;
}
return _TypeArgumentData(typeArguments, parent.argumentList.offset);
} else if (parent is MethodDeclaration) {
// invalid_override
if (extendedType != null && !extendedType.validate(context)) {
return null;
}
var typeParameters = parent.typeParameters;
if (_isInvalidIndex(typeParameters?.typeParameters)) {
return null;
}
return _TypeParameterData(typeParameters, parent.name.end);
} else if (node is TypeArgumentList && parent is ExtensionOverride) {
// wrong_number_of_type_arguments_extension
var argument = argumentValue.validate(context);
if (argument == null) {
return null;
}
if (_isInvalidIndex(node?.arguments)) {
return null;
}
return _TypeArgumentData(node, parent.extensionName.end);
}
return null;
}
void _applyToTypeArguments(
DartFileEditBuilder builder, DataDrivenFix fix, _TypeArgumentData data) {
var context = TemplateContext(getInvocation(fix.node), fix.utils);
var typeArguments = data.typeArguments;
if (typeArguments == null) {
// Adding the first type argument.
builder.addInsertion(data.newListOffset, (builder) {
builder.write('<');
argumentValue.writeOn(builder, context);
builder.write('>');
});
} else {
if (index == 0) {
// Inserting the type argument at the beginning of the list.
builder.addInsertion(typeArguments.leftBracket.end, (builder) {
argumentValue.writeOn(builder, context);
builder.write(', ');
});
} else {
// Inserting the type argument after an existing type argument.
var previous = typeArguments.arguments[index - 1];
builder.addInsertion(previous.end, (builder) {
builder.write(', ');
argumentValue.writeOn(builder, context);
});
}
}
}
void _applyToTypeParameters(
DartFileEditBuilder builder, DataDrivenFix fix, _TypeParameterData data) {
var context = TemplateContext(getInvocation(fix.node), fix.utils);
void writeParameter(DartEditBuilder builder) {
builder.write(name);
if (extendedType != null) {
builder.write(' extends ');
extendedType.writeOn(builder, context);
}
}
var typeParameters = data.typeParameters;
if (typeParameters == null) {
// Adding the first type argument.
builder.addInsertion(data.newListOffset, (builder) {
builder.write('<');
writeParameter(builder);
builder.write('>');
});
} else {
if (index == 0) {
// Inserting the type argument at the beginning of the list.
builder.addInsertion(typeParameters.leftBracket.end, (builder) {
writeParameter(builder);
builder.write(', ');
});
} else {
// Inserting the type argument after an existing type argument.
var previous = typeParameters.typeParameters[index - 1];
builder.addInsertion(previous.end, (builder) {
builder.write(', ');
writeParameter(builder);
});
}
}
}
bool _isInvalidIndex(List<AstNode> list) {
var length = list == null ? 0 : list.length;
return index > length;
}
}
/// The data returned when adding a type parameter.
class _Data {}
/// The data returned when updating a type argument list.
class _TypeArgumentData extends _Data {
/// The list of type arguments to which a new type argument is being added, or
/// `null` if the first type argument is being added.
final TypeArgumentList typeArguments;
/// The offset at which the type argument list should be inserted if
/// [typeArguments] is `null`.
final int newListOffset;
/// Initialize newly created data.
_TypeArgumentData(this.typeArguments, this.newListOffset);
}
/// The data returned when updating a type parameter list.
class _TypeParameterData extends _Data {
/// The list of type parameters to which a new type parameter is being added,
/// or `null` if the first type parameter is being added.
final TypeParameterList typeParameters;
/// The offset at which the type parameter list should be inserted if
/// [typeParameters] is `null`.
final int newListOffset;
/// Initialize newly created data.
_TypeParameterData(this.typeParameters, this.newListOffset);
}