blob: 98e219509ea6fde20ca5f39114886638356fc3ac [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:_fe_analyzer_shared/src/scanner/token.dart';
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/assist/assist.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 AddTypeAnnotation extends CorrectionProducer {
@override
AssistKind get assistKind => DartAssistKind.ADD_TYPE_ANNOTATION;
@override
FixKind get fixKind => DartFixKind.ADD_TYPE_ANNOTATION;
@override
Future<void> compute(ChangeBuilder builder) async {
var node = this.node;
if (node is SimpleIdentifier) {
var parent = node.parent;
if (parent is SimpleFormalParameter) {
await _forSimpleFormalParameter(builder, node, parent);
return;
}
}
while (node != null) {
if (node is VariableDeclarationList) {
await _forVariableDeclaration(builder, node);
return;
} else if (node is DeclaredIdentifier) {
await _forDeclaredIdentifier(builder, node);
return;
} else if (node is ForStatement) {
var forLoopParts = node.forLoopParts;
if (forLoopParts is ForEachParts) {
var offset = this.node.offset;
if (forLoopParts.iterable != null &&
offset < forLoopParts.iterable.offset) {
if (forLoopParts is ForEachPartsWithDeclaration) {
await _forDeclaredIdentifier(builder, forLoopParts.loopVariable);
}
}
}
return;
}
node = node.parent;
}
}
/// Configure the [utils] using the given [target].
void _configureTargetLocation(Object target) {
utils.targetClassElement = null;
if (target is AstNode) {
var targetClassDeclaration =
target.thisOrAncestorOfType<ClassDeclaration>();
if (targetClassDeclaration != null) {
utils.targetClassElement = targetClassDeclaration.declaredElement;
}
}
}
Future<void> _forDeclaredIdentifier(
ChangeBuilder builder, DeclaredIdentifier declaredIdentifier) async {
// Ensure that there isn't already a type annotation.
if (declaredIdentifier.type != null) {
return;
}
var type = declaredIdentifier.declaredElement.type;
if (type is! InterfaceType && type is! FunctionType) {
return;
}
_configureTargetLocation(node);
Future<bool> applyChange(ChangeBuilder builder) async {
var validChange = true;
await builder.addDartFileEdit(file, (builder) {
var keyword = declaredIdentifier.keyword;
if (keyword.keyword == Keyword.VAR) {
builder.addReplacement(range.token(keyword), (builder) {
validChange = builder.writeType(type);
});
} else {
builder.addInsertion(declaredIdentifier.identifier.offset, (builder) {
validChange = builder.writeType(type);
builder.write(' ');
});
}
});
return validChange;
}
if (await applyChange(_temporaryBuilder(builder))) {
await applyChange(builder);
}
}
Future<void> _forSimpleFormalParameter(ChangeBuilder builder,
SimpleIdentifier name, SimpleFormalParameter parameter) async {
// Ensure that there isn't already a type annotation.
if (parameter.type != null) {
return;
}
// Prepare the type.
var type = parameter.declaredElement.type;
// TODO(scheglov) If the parameter is in a method declaration, and if the
// method overrides a method that has a type for the corresponding
// parameter, it would be nice to copy down the type from the overridden
// method.
if (type is! InterfaceType) {
return;
}
_configureTargetLocation(node);
Future<bool> applyChange(ChangeBuilder builder) async {
var validChange = true;
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(name.offset, (builder) {
validChange = builder.writeType(type);
if (validChange) {
builder.write(' ');
}
});
});
return validChange;
}
if (await applyChange(_temporaryBuilder(builder))) {
await applyChange(builder);
}
}
Future<void> _forVariableDeclaration(
ChangeBuilder builder, VariableDeclarationList declarationList) async {
// Ensure that there isn't already a type annotation.
if (declarationList.type != null) {
return;
}
// Ensure that there is a single VariableDeclaration.
List<VariableDeclaration> variables = declarationList.variables;
if (variables.length != 1) {
return;
}
var variable = variables[0];
// Ensure that the selection is not after the name of the variable.
if (selectionOffset > variable.name.end) {
return;
}
// Ensure that there is an initializer to get the type from.
var initializer = variable.initializer;
if (initializer == null) {
return;
}
var type = initializer.staticType;
// prepare type source
if ((type is! InterfaceType || type.isDartCoreNull) &&
type is! FunctionType) {
return;
}
_configureTargetLocation(node);
Future<bool> applyChange(ChangeBuilder builder) async {
var validChange = true;
await builder.addDartFileEdit(file, (builder) {
var keyword = declarationList.keyword;
if (keyword?.keyword == Keyword.VAR) {
builder.addReplacement(range.token(keyword), (builder) {
validChange = builder.writeType(type);
});
} else {
builder.addInsertion(variable.offset, (builder) {
validChange = builder.writeType(type);
builder.write(' ');
});
}
});
return validChange;
}
if (await applyChange(_temporaryBuilder(builder))) {
await applyChange(builder);
}
}
ChangeBuilder _temporaryBuilder(ChangeBuilder builder) =>
ChangeBuilder(workspace: (builder as ChangeBuilderImpl).workspace);
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static AddTypeAnnotation newInstance() => AddTypeAnnotation();
}