| // 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); |
| } |
| } |