blob: 422549f16c0e5295629ca05bedcb78e30a461801 [file] [log] [blame]
// Copyright (c) 2015, 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 'dart:async';
import 'package:analysis_server/plugin/protocol/protocol.dart' hide ElementKind;
import 'package:analysis_server/src/provisional/edit/utilities/change_builder_core.dart';
import 'package:analysis_server/src/provisional/edit/utilities/change_builder_dart.dart';
import 'package:analysis_server/src/services/correction/name_suggestion.dart';
import 'package:analysis_server/src/services/correction/util.dart';
import 'package:analysis_server/src/utilities/change_builder_core.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
/**
* A [ChangeBuilder] used to build changes in Dart files.
*/
class DartChangeBuilderImpl extends ChangeBuilderImpl
implements DartChangeBuilder {
/**
* The analysis driver in which the files being edited were analyzed.
*/
final AnalysisDriver driver;
/**
* Initialize a newly created change builder.
*/
DartChangeBuilderImpl(this.driver);
@override
Future<DartFileEditBuilderImpl> createFileEditBuilder(
String path, int fileStamp) async {
AnalysisResult result = await driver.getResult(path);
return new DartFileEditBuilderImpl(this, path, fileStamp, result.unit);
}
}
/**
* An [EditBuilder] used to build edits in Dart files.
*/
class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
/**
* A utility class used to help build the source code.
*/
final CorrectionUtils utils;
/**
* Initialize a newly created builder to build a source edit.
*/
DartEditBuilderImpl(
DartFileEditBuilderImpl sourceFileEditBuilder, int offset, int length)
: utils = sourceFileEditBuilder.utils,
super(sourceFileEditBuilder, offset, length);
DartFileEditBuilderImpl get dartFileEditBuilder => fileEditBuilder;
@override
void writeClassDeclaration(String name,
{Iterable<DartType> interfaces,
bool isAbstract: false,
void memberWriter(),
Iterable<DartType> mixins,
String nameGroupName,
DartType superclass}) {
// TODO(brianwilkerson) Add support for type parameters, probably as a
// parameterWriter parameter.
// TODO(brianwilkerson) Add a superclassGroupName parameter.
if (isAbstract) {
write(Keyword.ABSTRACT.syntax);
write(' ');
}
write('class ');
if (nameGroupName == null) {
write(name);
} else {
addLinkedEdit(DartEditBuilder.NAME_GROUP_ID, (LinkedEditBuilder builder) {
write(name);
});
}
if (superclass != null) {
write(' extends ');
writeType(superclass, groupName: DartEditBuilder.SUPERCLASS_GROUP_ID);
} else if (mixins != null && mixins.isNotEmpty) {
write(' extends Object ');
}
writeTypes(mixins, prefix: ' with ');
writeTypes(interfaces, prefix: ' implements ');
writeln(' {');
if (memberWriter != null) {
writeln();
memberWriter();
writeln();
}
write('}');
}
//@override
void writeConstructorDeclaration(ClassElement classElement,
{ArgumentList argumentList,
SimpleIdentifier constructorName,
bool isConst: false}) {
// TODO(brianwilkerson) Clean up the API and add it to the public API.
//
// TODO(brianwilkerson) Support passing a list of final fields rather than
// an argument list.
if (isConst) {
write(Keyword.CONST.syntax);
write(' ');
}
write(classElement.name);
write('.');
if (constructorName != null) {
addLinkedEdit(DartEditBuilder.NAME_GROUP_ID, (LinkedEditBuilder builder) {
write(constructorName.name);
});
CompilationUnit unit = constructorName
.getAncestor((AstNode node) => node is CompilationUnit);
if (unit != null) {
CompilationUnitElement element = unit.element;
if (element != null) {
String referenceFile = element.source.fullName;
if (referenceFile == dartFileEditBuilder.fileEdit.file) {
dartFileEditBuilder.addLinkedPosition(constructorName.offset,
constructorName.length, DartEditBuilder.NAME_GROUP_ID);
}
}
}
}
if (argumentList != null) {
writeParametersMatchingArguments(argumentList);
} else {
write('()');
}
writeln(' {');
write(' }');
}
@override
void writeFieldDeclaration(String name,
{void initializerWriter(),
bool isConst: false,
bool isFinal: false,
bool isStatic: false,
String nameGroupName,
DartType type,
String typeGroupName}) {
if (isStatic) {
write(Keyword.STATIC.syntax);
write(' ');
}
bool typeRequired = true;
if (isConst) {
write(Keyword.CONST.syntax);
typeRequired = false;
} else if (isFinal) {
write(Keyword.FINAL.syntax);
typeRequired = false;
}
if (type != null) {
writeType(type, groupName: typeGroupName);
} else if (typeRequired) {
write(Keyword.VAR.syntax);
}
write(' ');
if (nameGroupName != null) {
addLinkedEdit(nameGroupName, (LinkedEditBuilder builder) {
write(name);
});
} else {
write(name);
}
if (initializerWriter != null) {
write(' = ');
initializerWriter();
}
write(';');
}
@override
void writeGetterDeclaration(String name,
{void bodyWriter(),
bool isStatic: false,
String nameGroupName,
DartType returnType,
String returnTypeGroupName}) {
if (isStatic) {
write(Keyword.STATIC.syntax);
write(' ');
}
if (returnType != null) {
writeType(returnType, groupName: returnTypeGroupName);
write(' ');
}
write(Keyword.GET.syntax);
write(' ');
if (nameGroupName != null) {
addLinkedEdit(nameGroupName, (LinkedEditBuilder builder) {
write(name);
});
} else {
write(name);
}
if (bodyWriter == null) {
write(' => null;');
} else {
write(' ');
bodyWriter();
}
}
@override
void writeOverrideOfInheritedMember(ExecutableElement member) {
// prepare environment
String prefix = utils.getIndent(1);
// may be property
String prefix2 = utils.getIndent(2);
ElementKind elementKind = member.kind;
bool isGetter = elementKind == ElementKind.GETTER;
bool isSetter = elementKind == ElementKind.SETTER;
bool isMethod = elementKind == ElementKind.METHOD;
bool isOperator = isMethod && (member as MethodElement).isOperator;
write(prefix);
if (isGetter) {
writeln('// TODO: implement ${member.displayName}');
write(prefix);
}
// @override
writeln('@override');
write(prefix);
// return type
// REVIEW: Added groupId
bool shouldReturn = writeType(member.type.returnType,
groupName: DartEditBuilder.RETURN_TYPE_GROUP_ID);
write(' ');
if (isGetter) {
write(Keyword.GET.syntax);
write(' ');
} else if (isSetter) {
write(Keyword.SET.syntax);
write(' ');
} else if (isOperator) {
write(Keyword.OPERATOR.syntax);
write(' ');
}
// name
write(member.displayName);
// parameters + body
if (isGetter) {
writeln(' => null;');
} else {
List<ParameterElement> parameters = member.parameters;
writeParameters(parameters);
writeln(' {');
// TO-DO
write(prefix2);
writeln('// TODO: implement ${member.displayName}');
// REVIEW: Added return statement.
if (shouldReturn) {
write(prefix2);
writeln('return null;');
}
// close method
write(prefix);
writeln('}');
}
}
@override
void writeParameters(Iterable<ParameterElement> parameters) {
write('(');
bool sawNamed = false;
bool sawPositional = false;
for (int i = 0; i < parameters.length; i++) {
ParameterElement parameter = parameters.elementAt(i);
if (i > 0) {
write(', ');
}
// may be optional
ParameterKind parameterKind = parameter.parameterKind;
if (parameterKind == ParameterKind.NAMED) {
if (!sawNamed) {
write('{');
sawNamed = true;
}
}
if (parameterKind == ParameterKind.POSITIONAL) {
if (!sawPositional) {
write('[');
sawPositional = true;
}
}
// parameter
writeParameterSource(parameter.type, parameter.name);
// default value
String defaultCode = parameter.defaultValueCode;
if (defaultCode != null) {
if (sawPositional) {
write(' = ');
} else {
write(': ');
}
write(defaultCode);
}
}
// close parameters
if (sawNamed) {
write('}');
}
if (sawPositional) {
write(']');
}
write(')');
}
@override
void writeParametersMatchingArguments(ArgumentList arguments) {
Set<String> excluded = new Set();
bool namedFound = false;
write('(');
List<Expression> argumentList = arguments.arguments;
for (int i = 0; i < argumentList.length; i++) {
Expression argument = argumentList[i];
DartType type = argument.bestType;
List<String> suggestions =
_getParameterNameSuggestions(excluded, type, argument, i);
String favorite = suggestions[0];
// append separator
if (i > 0) {
write(', ');
}
if (argument is NamedExpression) {
if (!namedFound) {
namedFound = true;
write('[');
}
favorite = argument.name.label.name;
}
// append type name
writeType(type, addSupertypeProposals: true, groupName: 'TYPE$i');
write(' ');
// append parameter name
excluded.add(favorite);
addLinkedEdit('ARG$i', (LinkedEditBuilder builder) {
builder.write(favorite);
builder.addSuggestions(LinkedEditSuggestionKind.PARAMETER, suggestions);
});
}
if (namedFound) {
write(']');
}
write(')');
}
@override
void writeParameterSource(DartType type, String name) {
String parameterSource = utils.getParameterSource(
type, name, dartFileEditBuilder.librariesToImport);
write(parameterSource);
}
@override
bool writeType(DartType type,
{bool addSupertypeProposals: false,
String groupName,
bool required: false}) {
if (type != null && !type.isDynamic) {
String typeSource =
utils.getTypeSource(type, dartFileEditBuilder.librariesToImport);
if (groupName != null) {
addLinkedEdit(groupName, (LinkedEditBuilder builder) {
write(typeSource);
if (addSupertypeProposals) {
_addSuperTypeProposals(builder, type, new Set<DartType>());
}
});
} else {
write(typeSource);
}
return true;
} else if (required) {
write(Keyword.VAR.syntax);
}
return false;
}
/**
* Write the code for a comma-separated list of [types], optionally prefixed
* by a [prefix]. If the list of [types] is `null` or does not return any
* types, then nothing will be written.
*/
void writeTypes(Iterable<DartType> types, {String prefix}) {
if (types == null || types.isEmpty) {
return;
}
bool first = true;
for (DartType type in types) {
if (first) {
if (prefix != null) {
write(prefix);
}
first = false;
} else {
write(', ');
}
writeType(type);
}
}
void _addSuperTypeProposals(
LinkedEditBuilder builder, DartType type, Set<DartType> alreadyAdded) {
if (type != null &&
type.element is ClassElement &&
alreadyAdded.add(type)) {
ClassElement element = type.element as ClassElement;
builder.addSuggestion(LinkedEditSuggestionKind.TYPE, element.name);
_addSuperTypeProposals(builder, element.supertype, alreadyAdded);
for (InterfaceType interfaceType in element.interfaces) {
_addSuperTypeProposals(builder, interfaceType, alreadyAdded);
}
}
}
/**
* Return a list containing the suggested names for a parmeter with the given
* [type] whose value in one location is computed by the given [expression].
* The list will not contain any names in the set of [excluded] names. The
* [index] is the index of the argument, used to create a name if no better
* name could be created. The first name in the list will be the best name.
*/
List<String> _getParameterNameSuggestions(
Set<String> excluded, DartType type, Expression expression, int index) {
List<String> suggestions =
getVariableNameSuggestionsForExpression(type, expression, excluded);
if (suggestions.length != 0) {
return suggestions;
}
return <String>['arg$index'];
}
}
/**
* A [FileEditBuilder] used to build edits for Dart files.
*/
class DartFileEditBuilderImpl extends FileEditBuilderImpl
implements DartFileEditBuilder {
/**
* The compilation unit to which the code will be added.
*/
CompilationUnit unit;
/**
* A utility class used to help build the source code.
*/
CorrectionUtils utils;
/**
* A set containing the sources of the libraries that need to be imported in
* order to make visible the names used in generated code.
*/
Set<Source> librariesToImport = new Set<Source>();
/**
* Initialize a newly created builder to build a source file edit within the
* change being built by the given [changeBuilder]. The file being edited has
* the given [source] and [timeStamp], and the given fully resolved [unit].
*/
DartFileEditBuilderImpl(DartChangeBuilderImpl changeBuilder, String path,
int timeStamp, this.unit)
: super(changeBuilder, path, timeStamp) {
utils = new CorrectionUtils(unit);
}
@override
DartEditBuilderImpl createEditBuilder(int offset, int length) {
return new DartEditBuilderImpl(this, offset, length);
}
}