Index: pkg/analysis_server/lib/src/utilities/change_builder_dart.dart |
diff --git a/pkg/analysis_server/lib/src/utilities/change_builder_dart.dart b/pkg/analysis_server/lib/src/utilities/change_builder_dart.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a337db28d8d9d4332a6de3e198989abc4672a57d |
--- /dev/null |
+++ b/pkg/analysis_server/lib/src/utilities/change_builder_dart.dart |
@@ -0,0 +1,412 @@ |
+// 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. |
+ |
+library analysis_server.src.utilities.change_builder_dart; |
+ |
+import 'package:analysis_server/src/protocol.dart' hide ElementKind; |
+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:analysis_server/utilities/change_builder_core.dart'; |
+import 'package:analysis_server/utilities/change_builder_dart.dart'; |
+import 'package:analyzer/src/generated/ast.dart'; |
+import 'package:analyzer/src/generated/element.dart'; |
+import 'package:analyzer/src/generated/engine.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 context in which the files being edited were analyzed. |
+ */ |
+ final AnalysisContext context; |
+ |
+ /** |
+ * Initialize a newly created change builder. |
+ */ |
+ DartChangeBuilderImpl(this.context); |
+ |
+ @override |
+ DartFileEditBuilderImpl createFileEditBuilder(Source source, int fileStamp) { |
+ return new DartFileEditBuilderImpl(this, source, fileStamp); |
+ } |
+} |
+ |
+/** |
+ * An [EditBuilder] used to build edits in Dart files. |
+ */ |
+class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder { |
+ /** |
+ * The string used when writing the 'abstract' modifier. |
+ */ |
+ static const String ABSTRACT_MODIFIER = 'abstract '; |
Paul Berry
2015/05/04 16:00:29
Personally, I'm a little dubious about making stri
|
+ |
+ /** |
+ * The string used when writing the 'const' modifier. |
+ */ |
+ static const String CONST_MODIFIER = 'const '; |
+ |
+ /** |
+ * A utility class used to help build the source code. |
+ */ |
+ final CorrectionUtils utils; |
+ |
+// /** |
+// * The string used when writing the 'final' modifier. |
+// */ |
+// static const String FINAL_MODIFIER = 'final '; |
+// |
+// /** |
+// * The string used when writing the 'static' modifier. |
+// */ |
+// static const String STATIC_MODIFIER = 'static '; |
+ |
+ /** |
+ * 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, {Iterator<DartType> interfaces, |
+ bool isAbstract: false, void memberWriter(), Iterator<DartType> mixins, DartType superclass}) { |
+ // TODO(brianwilkerson) Add support for type parameters |
+ // Map<String, DartType>, List<TypeParameter>? |
+ // |
+ // TODO(brianwilkerson) Make additional optional parameters visible in the |
+ // public API. |
+ if (isAbstract) { |
+ write(ABSTRACT_MODIFIER); |
+ } |
+ write('class '); |
+ addLinkedEdit(DartEditBuilder.NAME_GROUP_ID, (LinkedEditBuilder builder) { |
+ write(name); |
+ }); |
+ if (superclass != null) { |
Paul Berry
2015/05/04 16:00:29
Two things:
1. If superclass == null && mixins.is
|
+ write(' extends '); |
+ writeType(superclass, groupName: DartEditBuilder.SUPERCLASS_GROUP_ID); |
+ } |
+ writeTypes(mixins, prefix: ' with '); |
+ writeTypes(interfaces, prefix: ' implements '); |
+ writeln(' {'); |
+ if (memberWriter != null) { |
+ memberWriter(); |
+ } |
+ write('}'); |
+ } |
+ |
+ /** |
+ * 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(Iterator<DartType> types, {String prefix}) { |
+ if (types == null) { |
+ return; |
+ } |
+ bool first = true; |
+ while (types.moveNext()) { |
+ if (first) { |
+ if (prefix != null) { |
+ write(prefix); |
+ } |
+ first = false; |
+ } else { |
+ write(', '); |
+ } |
+ writeType(types.current); |
+ } |
+ } |
+ |
+ 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(CONST_MODIFIER); |
+ } |
+ 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 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('get '); |
+ } else if (isSetter) { |
+ write('set '); |
+ } else if (isOperator) { |
+ write('operator '); |
+ } |
+ // 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('var'); |
+ } |
+ return false; |
+ } |
+ |
+ 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 elements of the libraries that need to be imported in |
+ * order to make visible the names used in generated code. |
+ */ |
+ Set<LibraryElement> librariesToImport = new Set<LibraryElement>(); |
+ |
+ /** |
+ * 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]. |
+ */ |
+ DartFileEditBuilderImpl( |
+ DartChangeBuilderImpl changeBuilder, Source source, int timeStamp) |
+ : super(changeBuilder, source, timeStamp) { |
+ AnalysisContext context = changeBuilder.context; |
+ List<Source> librariesContaining = context.getLibrariesContaining(source); |
+ if (librariesContaining.length < 1) { |
+ throw new StateError('Cannot build edits for ${source.fullName}'); |
+ } |
+ unit = context.resolveCompilationUnit2(source, librariesContaining[0]); |
+ utils = new CorrectionUtils(unit); |
+ } |
+ |
+ @override |
+ DartEditBuilderImpl createEditBuilder(int offset, int length) { |
+ return new DartEditBuilderImpl(this, offset, length); |
+ } |
+} |