mirror of
https://github.com/dart-lang/sdk
synced 2024-10-04 18:36:29 +00:00
Add utilities for creating SourceChanges
R=scheglov@google.com Review URL: https://codereview.chromium.org//1110583002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@45461 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
parent
c014b52640
commit
d741aaf58d
249
pkg/analysis_server/lib/src/utilities/change_builder_core.dart
Normal file
249
pkg/analysis_server/lib/src/utilities/change_builder_core.dart
Normal file
|
@ -0,0 +1,249 @@
|
|||
// 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_core;
|
||||
|
||||
import 'package:analysis_server/src/protocol.dart';
|
||||
import 'package:analysis_server/utilities/change_builder_core.dart';
|
||||
import 'package:analyzer/src/generated/source.dart';
|
||||
|
||||
/**
|
||||
* A builder used to build a [SourceChange].
|
||||
*/
|
||||
class ChangeBuilderImpl implements ChangeBuilder {
|
||||
/**
|
||||
* The end-of-line marker used in the file being edited, or `null` if the
|
||||
* default marker should be used.
|
||||
*/
|
||||
String eol = null;
|
||||
|
||||
/**
|
||||
* The change that is being built.
|
||||
*/
|
||||
final SourceChange _change = new SourceChange('');
|
||||
|
||||
/**
|
||||
* A table mapping group ids to the associated linked edit groups.
|
||||
*/
|
||||
final Map<String, LinkedEditGroup> _linkedEditGroups =
|
||||
<String, LinkedEditGroup>{};
|
||||
|
||||
/**
|
||||
* Initialize a newly created change builder.
|
||||
*/
|
||||
ChangeBuilderImpl();
|
||||
|
||||
@override
|
||||
SourceChange get sourceChange {
|
||||
_linkedEditGroups.forEach((String name, LinkedEditGroup group) {
|
||||
_change.addLinkedEditGroup(group);
|
||||
});
|
||||
_linkedEditGroups.clear();
|
||||
return _change;
|
||||
}
|
||||
|
||||
@override
|
||||
void addFileEdit(Source source, int fileStamp,
|
||||
void buildFileEdit(FileEditBuilder builder)) {
|
||||
FileEditBuilderImpl builder = createFileEditBuilder(source, fileStamp);
|
||||
try {
|
||||
buildFileEdit(builder);
|
||||
} finally {
|
||||
_change.addFileEdit(builder.fileEdit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a [FileEditBuilder] that can be used to build edits to
|
||||
* the given [source].
|
||||
*/
|
||||
FileEditBuilderImpl createFileEditBuilder(Source source, int fileStamp) {
|
||||
return new FileEditBuilderImpl(this, source, fileStamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the linked edit group with the given [groupName], creating it if it
|
||||
* did not already exist.
|
||||
*/
|
||||
LinkedEditGroup getLinkedEditGroup(String groupName) {
|
||||
LinkedEditGroup group = _linkedEditGroups[groupName];
|
||||
if (group == null) {
|
||||
group = new LinkedEditGroup.empty();
|
||||
_linkedEditGroups[groupName] = group;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder used to build a [SourceEdit] as part of a [SourceFileEdit].
|
||||
*/
|
||||
class EditBuilderImpl implements EditBuilder {
|
||||
/**
|
||||
* The builder being used to create the source file edit of which the source
|
||||
* edit will be a part.
|
||||
*/
|
||||
final FileEditBuilderImpl fileEditBuilder;
|
||||
|
||||
/**
|
||||
* The offset of the region being replaced.
|
||||
*/
|
||||
final int offset;
|
||||
|
||||
/**
|
||||
* The length of the region being replaced.
|
||||
*/
|
||||
final int length;
|
||||
|
||||
/**
|
||||
* The end-of-line marker used in the file being edited, or `null` if the
|
||||
* default marker should be used.
|
||||
*/
|
||||
String _eol = null;
|
||||
|
||||
/**
|
||||
* The buffer in which the content of the edit is being composed.
|
||||
*/
|
||||
final StringBuffer _buffer = new StringBuffer();
|
||||
|
||||
/**
|
||||
* Initialize a newly created builder to build a source edit.
|
||||
*/
|
||||
EditBuilderImpl(this.fileEditBuilder, this.offset, this.length) {
|
||||
_eol = fileEditBuilder.changeBuilder.eol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return an edit representing the replacement of a region of the
|
||||
* file with the accumulated text.
|
||||
*/
|
||||
SourceEdit get sourceEdit =>
|
||||
new SourceEdit(offset, length, _buffer.toString());
|
||||
|
||||
@override
|
||||
void addLinkedEdit(
|
||||
String groupName, void buildLinkedEdit(LinkedEditBuilder builder)) {
|
||||
LinkedEditBuilderImpl builder = createLinkedEditBuilder();
|
||||
int start = offset + _buffer.length;
|
||||
try {
|
||||
buildLinkedEdit(builder);
|
||||
} finally {
|
||||
int end = offset + _buffer.length;
|
||||
int length = end - start;
|
||||
Position position = new Position(fileEditBuilder.fileEdit.file, start);
|
||||
LinkedEditGroup group =
|
||||
fileEditBuilder.changeBuilder.getLinkedEditGroup(groupName);
|
||||
group.addPosition(position, length);
|
||||
for (LinkedEditSuggestion suggestion in builder.suggestions) {
|
||||
group.addSuggestion(suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LinkedEditBuilderImpl createLinkedEditBuilder() {
|
||||
return new LinkedEditBuilderImpl(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(String string) {
|
||||
_buffer.write(string);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeln([String string]) {
|
||||
_buffer.write(string);
|
||||
if (_eol == null) {
|
||||
_buffer.writeln();
|
||||
} else {
|
||||
_buffer.write(_eol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder used to build a [SourceFileEdit] within a [SourceChange].
|
||||
*/
|
||||
class FileEditBuilderImpl implements FileEditBuilder {
|
||||
/**
|
||||
* The builder being used to create the source change of which the source file
|
||||
* edit will be a part.
|
||||
*/
|
||||
final ChangeBuilderImpl changeBuilder;
|
||||
|
||||
/**
|
||||
* The source file edit that is being built.
|
||||
*/
|
||||
final SourceFileEdit fileEdit;
|
||||
|
||||
/**
|
||||
* 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 [timeStamp] and [timeStamp].
|
||||
*/
|
||||
FileEditBuilderImpl(this.changeBuilder, Source source, int timeStamp)
|
||||
: fileEdit = new SourceFileEdit(source.fullName, timeStamp);
|
||||
|
||||
@override
|
||||
void addInsertion(int offset, void buildEdit(EditBuilder builder)) {
|
||||
EditBuilderImpl builder = createEditBuilder(offset, 0);
|
||||
try {
|
||||
buildEdit(builder);
|
||||
} finally {
|
||||
fileEdit.add(builder.sourceEdit);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void addLinkedPosition(int offset, int length, String groupName) {
|
||||
LinkedEditGroup group = changeBuilder.getLinkedEditGroup(groupName);
|
||||
Position position = new Position(fileEdit.file, offset);
|
||||
group.addPosition(position, length);
|
||||
}
|
||||
|
||||
@override
|
||||
void addReplacement(
|
||||
int offset, int length, void buildEdit(EditBuilder builder)) {
|
||||
EditBuilderImpl builder = createEditBuilder(offset, length);
|
||||
try {
|
||||
buildEdit(builder);
|
||||
} finally {
|
||||
fileEdit.add(builder.sourceEdit);
|
||||
}
|
||||
}
|
||||
|
||||
EditBuilderImpl createEditBuilder(int offset, int length) {
|
||||
return new EditBuilderImpl(this, offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder used to build a [LinkedEdit] region within an edit.
|
||||
*/
|
||||
class LinkedEditBuilderImpl implements LinkedEditBuilder {
|
||||
final EditBuilderImpl editBuilder;
|
||||
|
||||
final List<LinkedEditSuggestion> suggestions = <LinkedEditSuggestion>[];
|
||||
|
||||
LinkedEditBuilderImpl(this.editBuilder);
|
||||
|
||||
@override
|
||||
void addSuggestion(LinkedEditSuggestionKind kind, String value) {
|
||||
suggestions.add(new LinkedEditSuggestion(value, kind));
|
||||
}
|
||||
|
||||
@override
|
||||
void addSuggestions(LinkedEditSuggestionKind kind, List<String> values) {
|
||||
values.forEach((value) => addSuggestion(kind, value));
|
||||
}
|
||||
|
||||
@override
|
||||
void write(String string) {
|
||||
editBuilder.write(string);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeln([String string]) {
|
||||
editBuilder.writeln(string);
|
||||
}
|
||||
}
|
412
pkg/analysis_server/lib/src/utilities/change_builder_dart.dart
Normal file
412
pkg/analysis_server/lib/src/utilities/change_builder_dart.dart
Normal file
|
@ -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 ';
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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);
|
||||
}
|
||||
}
|
123
pkg/analysis_server/lib/utilities/change_builder_core.dart
Normal file
123
pkg/analysis_server/lib/utilities/change_builder_core.dart
Normal file
|
@ -0,0 +1,123 @@
|
|||
// 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.utilities.change_builder_core;
|
||||
|
||||
import 'package:analysis_server/src/utilities/change_builder_core.dart';
|
||||
import 'package:analysis_server/src/protocol.dart';
|
||||
import 'package:analyzer/src/generated/source.dart';
|
||||
|
||||
/**
|
||||
* A builder used to build a [SourceChange].
|
||||
*
|
||||
* Clients are not expected to subtype this class.
|
||||
*/
|
||||
abstract class ChangeBuilder {
|
||||
/**
|
||||
* Initialize a newly created change builder.
|
||||
*/
|
||||
factory ChangeBuilder() = ChangeBuilderImpl;
|
||||
|
||||
/**
|
||||
* Return the source change that was built.
|
||||
*/
|
||||
SourceChange get sourceChange;
|
||||
|
||||
/**
|
||||
* Use the [buildFileEdit] function to create a collection of edits to the
|
||||
* given [source]. The edits will be added to the source change that is being
|
||||
* built. The [timeStamp] is the time at which the [source] was last modified
|
||||
* and is used by clients to ensure that it is safe to apply the edits.
|
||||
*/
|
||||
void addFileEdit(Source source, int timeStamp,
|
||||
void buildFileEdit(FileEditBuilder builder));
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder used to build a [SourceEdit] as part of a [SourceFileEdit].
|
||||
*
|
||||
* Clients are not expected to subtype this class.
|
||||
*/
|
||||
abstract class EditBuilder {
|
||||
/**
|
||||
* Add a region of text that is part of the linked edit group with the given
|
||||
* [groupName]. The [buildLinkedEdit] function is used to write the content of
|
||||
* the region of text and to add suggestions for other possible values for
|
||||
* that region.
|
||||
*/
|
||||
void addLinkedEdit(
|
||||
String groupName, void buildLinkedEdit(LinkedEditBuilder builder));
|
||||
|
||||
/**
|
||||
* Add the given [string] to the content of the current edit.
|
||||
*/
|
||||
void write(String string);
|
||||
|
||||
/**
|
||||
* Add the given [string] to the content of the current edit and then add an
|
||||
* end-of-line marker.
|
||||
*/
|
||||
void writeln([String string]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder used to build a [SourceFileEdit] within a [SourceChange].
|
||||
*
|
||||
* Clients are not expected to subtype this class.
|
||||
*/
|
||||
abstract class FileEditBuilder {
|
||||
/**
|
||||
* Add an insertion of text at the given [offset]. The [offset] is relative to
|
||||
* the original source. The [buildEdit] function is used to write the text to
|
||||
* be inserted. This is fully equivalent to
|
||||
*
|
||||
* addReplacement(offset, 0, buildEdit);
|
||||
*/
|
||||
void addInsertion(int offset, void buildEdit(EditBuilder builder));
|
||||
|
||||
/**
|
||||
* Add the region of text starting at the given [offset] and continuing for
|
||||
* the given [length] to the linked edit group with the given [groupName].
|
||||
* The [offset] is relative to the original source. This is typically used to
|
||||
* include pre-existing regions of text in a group.
|
||||
*/
|
||||
void addLinkedPosition(int offset, int length, String groupName);
|
||||
|
||||
/**
|
||||
* Add a replacement of text starting at the given [offset] and continuing for
|
||||
* the given [length]. The [offset] is relative to the original source. The
|
||||
* [buildEdit] function is used to write the text that will replace the
|
||||
* specified region.
|
||||
*/
|
||||
void addReplacement(
|
||||
int offset, int length, void buildEdit(EditBuilder builder));
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder used to build a [LinkedEdit] region within an edit.
|
||||
*
|
||||
* Clients are not expected to subtype this class.
|
||||
*/
|
||||
abstract class LinkedEditBuilder {
|
||||
/**
|
||||
* Add the given [value] as a suggestion with the given [kind].
|
||||
*/
|
||||
void addSuggestion(LinkedEditSuggestionKind kind, String value);
|
||||
|
||||
/**
|
||||
* Add each of the given [values] as a suggestion with the given [kind].
|
||||
*/
|
||||
void addSuggestions(LinkedEditSuggestionKind kind, Iterable<String> values);
|
||||
|
||||
/**
|
||||
* Add the given [string] to the content of the current edit.
|
||||
*/
|
||||
void write(String string);
|
||||
|
||||
/**
|
||||
* Add the given [string] to the content of the current edit and then add an
|
||||
* end-of-line marker.
|
||||
*/
|
||||
void writeln([String string]);
|
||||
}
|
98
pkg/analysis_server/lib/utilities/change_builder_dart.dart
Normal file
98
pkg/analysis_server/lib/utilities/change_builder_dart.dart
Normal file
|
@ -0,0 +1,98 @@
|
|||
// 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.utilities.change_builder_dart;
|
||||
|
||||
import 'package:analysis_server/src/utilities/change_builder_dart.dart';
|
||||
import 'package:analysis_server/utilities/change_builder_core.dart';
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart';
|
||||
|
||||
/**
|
||||
* A [ChangeBuilder] used to build changes in Dart files.
|
||||
*
|
||||
* Clients are not expected to subtype this class.
|
||||
*/
|
||||
abstract class DartChangeBuilder extends ChangeBuilder {
|
||||
/**
|
||||
* Initialize a newly created change builder.
|
||||
*/
|
||||
factory DartChangeBuilder(AnalysisContext context) = DartChangeBuilderImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* An [EditBuilder] used to build edits in Dart files.
|
||||
*
|
||||
* Clients are not expected to subtype this class.
|
||||
*/
|
||||
abstract class DartEditBuilder extends EditBuilder {
|
||||
/**
|
||||
* The group-id used for the name of a declaration.
|
||||
*/
|
||||
static const String NAME_GROUP_ID = 'NAME';
|
||||
|
||||
/**
|
||||
* The group-id used for the return type of a function, getter or method.
|
||||
*/
|
||||
static const String RETURN_TYPE_GROUP_ID = 'RETURN_TYPE';
|
||||
|
||||
/**
|
||||
* The group-id used for the name of the superclass in a class declaration.
|
||||
*/
|
||||
static const String SUPERCLASS_GROUP_ID = 'SUPERCLASS';
|
||||
|
||||
/**
|
||||
* Write the code for a declaration of a class with the given [name]. If
|
||||
* [isAbstract] is `true`, then the class will be abstract. If a [superclass]
|
||||
* is given then it will be the superclass of the class.
|
||||
*/
|
||||
void writeClassDeclaration(String name,
|
||||
{bool isAbstract: false, DartType superclass});
|
||||
|
||||
/**
|
||||
* Append a placeholder for an override of an inherited [member].
|
||||
*/
|
||||
void writeOverrideOfInheritedMember(ExecutableElement member);
|
||||
|
||||
/**
|
||||
* Write the code for a list of [parameters], including the surrounding
|
||||
* parentheses.
|
||||
*/
|
||||
void writeParameters(Iterable<ParameterElement> parameters);
|
||||
|
||||
/**
|
||||
* Write the code for a list of parameters that would match the given list of
|
||||
* [arguments], including the surrounding parentheses.
|
||||
*/
|
||||
void writeParametersMatchingArguments(ArgumentList arguments);
|
||||
|
||||
/**
|
||||
* Write the code for a single parameter with the given [type] and [name].
|
||||
* The [type] can be `null` if no type is to be specified for the parameter.
|
||||
*/
|
||||
void writeParameterSource(DartType type, String name);
|
||||
|
||||
/**
|
||||
* Write the code for a type annotation for the given [type]. If the [type] is
|
||||
* either `null` or represents the type 'dynamic', then the behavior depends
|
||||
* on whether a type is [required]. If [required] is `true`, then 'var' will
|
||||
* be written; otherwise, nothing is written.
|
||||
*
|
||||
* If the [groupName] is not `null`, then the name of the type (including type
|
||||
* parameters) will be included as a region in the linked edit group with that
|
||||
* name. If the [groupName] is not `null` and [addSupertypeProposals] is
|
||||
* `true`, then all of the supertypes of the [type] will be added as
|
||||
* suggestions for alternatives to the type name.
|
||||
*/
|
||||
bool writeType(DartType type,
|
||||
{bool addSupertypeProposals: false, String groupName, bool required: false});
|
||||
}
|
||||
|
||||
/**
|
||||
* A [FileEditBuilder] used to build edits for Dart files.
|
||||
*
|
||||
* Clients are not expected to subtype this class.
|
||||
*/
|
||||
abstract class DartFileEditBuilder extends FileEditBuilder {}
|
19
pkg/analysis_server/test/src/test_all.dart
Normal file
19
pkg/analysis_server/test/src/test_all.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2014, 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 test.src;
|
||||
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
import 'utilities/test_all.dart' as utilities_all;
|
||||
|
||||
/**
|
||||
* Utility for manually running all tests.
|
||||
*/
|
||||
main() {
|
||||
groupSep = ' | ';
|
||||
group('analysis_server', () {
|
||||
utilities_all.main();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
// 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.test.src.utilities.change_builder_core_test;
|
||||
|
||||
import 'package:analysis_server/src/utilities/change_builder_core.dart';
|
||||
import 'package:analysis_server/src/protocol.dart';
|
||||
import 'package:analysis_server/utilities/change_builder_core.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import '../../domain_execution_test.dart';
|
||||
|
||||
main() {
|
||||
groupSep = ' | ';
|
||||
defineReflectiveTests(ChangeBuilderImplTest);
|
||||
defineReflectiveTests(EditBuilderImplTest);
|
||||
defineReflectiveTests(FileEditBuilderImplTest);
|
||||
defineReflectiveTests(LinkedEditBuilderImplTest);
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class ChangeBuilderImplTest {
|
||||
void test_createFileEditBuilder() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
TestSource source = new TestSource('/test.dart');
|
||||
int timeStamp = 54;
|
||||
FileEditBuilderImpl fileEditBuilder = builder.createFileEditBuilder(source, timeStamp);
|
||||
expect(fileEditBuilder, new isInstanceOf<FileEditBuilder>());
|
||||
SourceFileEdit fileEdit = fileEditBuilder.fileEdit;
|
||||
expect(fileEdit.file, source.fullName);
|
||||
expect(fileEdit.fileStamp, timeStamp);
|
||||
}
|
||||
|
||||
void test_getLinkedEditGroup() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
LinkedEditGroup group = builder.getLinkedEditGroup('a');
|
||||
expect(identical(builder.getLinkedEditGroup('b'), group), isFalse);
|
||||
expect(identical(builder.getLinkedEditGroup('a'), group), isTrue);
|
||||
}
|
||||
|
||||
void test_sourceChange_noChanges() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
SourceChange sourceChange = builder.sourceChange;
|
||||
expect(sourceChange, isNotNull);
|
||||
expect(sourceChange.edits, isEmpty);
|
||||
expect(sourceChange.linkedEditGroups, isEmpty);
|
||||
expect(sourceChange.message, isEmpty);
|
||||
expect(sourceChange.selection, isNull);
|
||||
}
|
||||
|
||||
void test_sourceChange_oneChange() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
TestSource source = new TestSource('/test.dart');
|
||||
builder.addFileEdit(source, 0, (FileEditBuilder builder) {});
|
||||
builder.getLinkedEditGroup('a');
|
||||
SourceChange sourceChange = builder.sourceChange;
|
||||
expect(sourceChange, isNotNull);
|
||||
expect(sourceChange.edits, hasLength(1));
|
||||
expect(sourceChange.linkedEditGroups, hasLength(1));
|
||||
expect(sourceChange.message, isEmpty);
|
||||
expect(sourceChange.selection, isNull);
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class EditBuilderImplTest {
|
||||
TestSource source = new TestSource('/test.dart');
|
||||
|
||||
void test_addLinkedEdit() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
int offset = 10;
|
||||
String text = 'content';
|
||||
builder.addFileEdit(source, 0, (FileEditBuilderImpl builder) {
|
||||
builder.addInsertion(10, (EditBuilderImpl builder) {
|
||||
builder.addLinkedEdit('a', (LinkedEditBuilder builder) {
|
||||
builder.write(text);
|
||||
});
|
||||
SourceEdit sourceEdit = builder.sourceEdit;
|
||||
expect(sourceEdit.replacement, text);
|
||||
});
|
||||
});
|
||||
SourceChange sourceChange = builder.sourceChange;
|
||||
expect(sourceChange, isNotNull);
|
||||
List<LinkedEditGroup> groups = sourceChange.linkedEditGroups;
|
||||
expect(groups, hasLength(1));
|
||||
LinkedEditGroup group = groups[0];
|
||||
expect(group, isNotNull);
|
||||
expect(group.length, text.length);
|
||||
List<Position> positions = group.positions;
|
||||
expect(positions, hasLength(1));
|
||||
expect(positions[0].offset, offset);
|
||||
}
|
||||
|
||||
void test_createLinkedEditBuilder() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
builder.addFileEdit(source, 0, (FileEditBuilderImpl builder) {
|
||||
builder.addInsertion(10, (EditBuilderImpl builder) {
|
||||
LinkedEditBuilderImpl linkBuilder = builder.createLinkedEditBuilder();
|
||||
expect(linkBuilder, new isInstanceOf<LinkedEditBuilder>());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void test_write() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
int timeStamp = 93;
|
||||
int offset = 10;
|
||||
String text = 'write';
|
||||
builder.addFileEdit(source, timeStamp, (FileEditBuilderImpl builder) {
|
||||
builder.addInsertion(offset, (EditBuilderImpl builder) {
|
||||
builder.write(text);
|
||||
});
|
||||
});
|
||||
|
||||
SourceChange sourceChange = builder.sourceChange;
|
||||
expect(sourceChange, isNotNull);
|
||||
|
||||
List<SourceFileEdit> fileEdits = sourceChange.edits;
|
||||
expect(fileEdits, hasLength(1));
|
||||
SourceFileEdit fileEdit = fileEdits[0];
|
||||
expect(fileEdit, isNotNull);
|
||||
expect(fileEdit.file, source.fullName);
|
||||
expect(fileEdit.fileStamp, timeStamp);
|
||||
|
||||
List<SourceEdit> edits = fileEdit.edits;
|
||||
expect(edits, hasLength(1));
|
||||
SourceEdit edit = edits[0];
|
||||
expect(edit, isNotNull);
|
||||
expect(edit.offset, offset);
|
||||
expect(edit.length, 0);
|
||||
expect(edit.replacement, text);
|
||||
}
|
||||
|
||||
void test_writeln() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
int timeStamp = 39;
|
||||
int offset = 52;
|
||||
int length = 12;
|
||||
String text = 'writeln';
|
||||
builder.addFileEdit(source, timeStamp, (FileEditBuilderImpl builder) {
|
||||
builder.addReplacement(offset, length, (EditBuilderImpl builder) {
|
||||
builder.writeln(text);
|
||||
});
|
||||
});
|
||||
|
||||
SourceChange sourceChange = builder.sourceChange;
|
||||
expect(sourceChange, isNotNull);
|
||||
|
||||
List<SourceFileEdit> fileEdits = sourceChange.edits;
|
||||
expect(fileEdits, hasLength(1));
|
||||
SourceFileEdit fileEdit = fileEdits[0];
|
||||
expect(fileEdit, isNotNull);
|
||||
expect(fileEdit.file, source.fullName);
|
||||
expect(fileEdit.fileStamp, timeStamp);
|
||||
|
||||
List<SourceEdit> edits = fileEdit.edits;
|
||||
expect(edits, hasLength(1));
|
||||
SourceEdit edit = edits[0];
|
||||
expect(edit, isNotNull);
|
||||
expect(edit.offset, offset);
|
||||
expect(edit.length, length);
|
||||
expect(edit.replacement == '$text\n' || edit.replacement == '$text\r\n', isTrue);
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class FileEditBuilderImplTest {
|
||||
TestSource source = new TestSource('/test.dart');
|
||||
|
||||
void test_addInsertion() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
builder.addFileEdit(source, 0, (FileEditBuilderImpl builder) {
|
||||
builder.addInsertion(10, (EditBuilderImpl builder) {
|
||||
expect(builder, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void test_addLinkedPosition() {
|
||||
ChangeBuilderImpl changeBuilder = new ChangeBuilderImpl();
|
||||
String groupName = 'a';
|
||||
changeBuilder.addFileEdit(source, 0, (FileEditBuilderImpl builder) {
|
||||
builder.addLinkedPosition(3, 6, groupName);
|
||||
});
|
||||
|
||||
LinkedEditGroup group = changeBuilder.getLinkedEditGroup(groupName);
|
||||
List<Position> positions = group.positions;
|
||||
expect(positions, hasLength(1));
|
||||
Position position = positions[0];
|
||||
expect(position.file, source.fullName);
|
||||
expect(position.offset, 3);
|
||||
expect(group.length, 6);
|
||||
}
|
||||
|
||||
void test_addReplacement() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
builder.addFileEdit(source, 0, (FileEditBuilderImpl builder) {
|
||||
builder.addReplacement(4, 5, (EditBuilderImpl builder) {
|
||||
expect(builder, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void test_createEditBuilder() {
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
builder.addFileEdit(source, 0, (FileEditBuilderImpl builder) {
|
||||
int offset = 4;
|
||||
int length = 5;
|
||||
EditBuilderImpl editBuilder = builder.createEditBuilder(offset, length);
|
||||
expect(editBuilder, new isInstanceOf<EditBuilder>());
|
||||
SourceEdit sourceEdit = editBuilder.sourceEdit;
|
||||
expect(sourceEdit.length, length);
|
||||
expect(sourceEdit.offset, offset);
|
||||
expect(sourceEdit.replacement, isEmpty);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class LinkedEditBuilderImplTest {
|
||||
TestSource source = new TestSource('/test.dart');
|
||||
|
||||
void test_addSuggestion() {
|
||||
String groupName = 'a';
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
builder.addFileEdit(source, 0, (FileEditBuilderImpl builder) {
|
||||
builder.addInsertion(10, (EditBuilderImpl builder) {
|
||||
builder.addLinkedEdit(groupName, (LinkedEditBuilderImpl builder) {
|
||||
builder.addSuggestion(LinkedEditSuggestionKind.TYPE, 'A');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
LinkedEditGroup group = builder.getLinkedEditGroup(groupName);
|
||||
expect(group.suggestions, hasLength(1));
|
||||
}
|
||||
|
||||
void test_addSuggestions() {
|
||||
String groupName = 'a';
|
||||
ChangeBuilderImpl builder = new ChangeBuilderImpl();
|
||||
builder.addFileEdit(source, 0, (FileEditBuilderImpl builder) {
|
||||
builder.addInsertion(10, (EditBuilderImpl builder) {
|
||||
builder.addLinkedEdit(groupName, (LinkedEditBuilderImpl builder) {
|
||||
builder.addSuggestions(LinkedEditSuggestionKind.TYPE, ['A', 'B']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
LinkedEditGroup group = builder.getLinkedEditGroup(groupName);
|
||||
expect(group.suggestions, hasLength(2));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,438 @@
|
|||
// 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.test.src.utilities.change_builder_dart_test;
|
||||
|
||||
import 'package:analysis_server/src/protocol.dart';
|
||||
import 'package:analysis_server/src/utilities/change_builder_core.dart';
|
||||
import 'package:analysis_server/src/utilities/change_builder_dart.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/source.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
import '../../abstract_context.dart';
|
||||
import '../../domain_execution_test.dart';
|
||||
|
||||
main() {
|
||||
groupSep = ' | ';
|
||||
defineReflectiveTests(DartChangeBuilderImplTest);
|
||||
defineReflectiveTests(DartEditBuilderImplTest);
|
||||
defineReflectiveTests(DartFileEditBuilderImplTest);
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class DartChangeBuilderImplTest extends AbstractContextTest {
|
||||
void test_createFileEditBuilder() {
|
||||
Source source = addSource('/test.dart', 'library test;');
|
||||
resolveLibraryUnit(source);
|
||||
int timeStamp = 54;
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
DartFileEditBuilderImpl fileEditBuilder =
|
||||
builder.createFileEditBuilder(source, timeStamp);
|
||||
expect(fileEditBuilder, new isInstanceOf<DartFileEditBuilder>());
|
||||
SourceFileEdit fileEdit = fileEditBuilder.fileEdit;
|
||||
expect(fileEdit.file, source.fullName);
|
||||
expect(fileEdit.fileStamp, timeStamp);
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class DartEditBuilderImplTest extends AbstractContextTest {
|
||||
SourceEdit getEdit(DartChangeBuilderImpl builder) {
|
||||
SourceChange sourceChange = builder.sourceChange;
|
||||
expect(sourceChange, isNotNull);
|
||||
List<SourceFileEdit> fileEdits = sourceChange.edits;
|
||||
expect(fileEdits, hasLength(1));
|
||||
SourceFileEdit fileEdit = fileEdits[0];
|
||||
expect(fileEdit, isNotNull);
|
||||
List<SourceEdit> edits = fileEdit.edits;
|
||||
expect(edits, hasLength(1));
|
||||
return edits[0];
|
||||
}
|
||||
|
||||
void test_writeClassDeclaration_isAbstract() {
|
||||
Source source = addSource('/test.dart', '');
|
||||
resolveLibraryUnit(source);
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(0, (DartEditBuilder builder) {
|
||||
builder.writeClassDeclaration('C', isAbstract: true);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('abstract class C { }'));
|
||||
}
|
||||
|
||||
void test_writeClassDeclaration_nameOnly() {
|
||||
Source source = addSource('/test.dart', '');
|
||||
resolveLibraryUnit(source);
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(0, (DartEditBuilder builder) {
|
||||
builder.writeClassDeclaration('C');
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('class C { }'));
|
||||
|
||||
List<LinkedEditGroup> linkedEditGroups =
|
||||
builder.sourceChange.linkedEditGroups;
|
||||
expect(linkedEditGroups, hasLength(1));
|
||||
LinkedEditGroup group = linkedEditGroups[0];
|
||||
expect(group.length, 1);
|
||||
expect(group.positions, hasLength(1));
|
||||
}
|
||||
|
||||
void test_writeClassDeclaration_superclass() {
|
||||
Source source = addSource('/test.dart', 'class B {}');
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
ClassDeclaration declaration = unit.declarations[0];
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(0, (DartEditBuilder builder) {
|
||||
builder.writeClassDeclaration('C',
|
||||
superclass: declaration.element.type);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('class C extends B { }'));
|
||||
}
|
||||
|
||||
void test_writeOverrideOfInheritedMember() {
|
||||
String content = '''
|
||||
class A {
|
||||
A add(A a) => null;
|
||||
}
|
||||
class B extends A {
|
||||
}''';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
ClassDeclaration declaration = unit.declarations[0];
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeOverrideOfInheritedMember(declaration.element.methods[0]);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('''
|
||||
@override
|
||||
A add(A a) {
|
||||
// TODO: implement add
|
||||
return null;
|
||||
}'''));
|
||||
}
|
||||
|
||||
void test_writeParameters_named() {
|
||||
String content = 'f(int i, {String s}) {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
FunctionDeclaration f = unit.declarations[0];
|
||||
FormalParameterList parameters = f.functionExpression.parameters;
|
||||
Iterable elements = parameters.parameters
|
||||
.map((FormalParameter parameter) => parameter.element);
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeParameters(elements);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('(int i, {String s})'));
|
||||
}
|
||||
|
||||
void test_writeParameters_positional() {
|
||||
String content = 'f(int i, [String s]) {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
FunctionDeclaration f = unit.declarations[0];
|
||||
FormalParameterList parameters = f.functionExpression.parameters;
|
||||
Iterable elements = parameters.parameters
|
||||
.map((FormalParameter parameter) => parameter.element);
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeParameters(elements);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('(int i, [String s])'));
|
||||
}
|
||||
|
||||
void test_writeParameters_required() {
|
||||
String content = 'f(int i, String s) {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
FunctionDeclaration f = unit.declarations[0];
|
||||
FormalParameterList parameters = f.functionExpression.parameters;
|
||||
Iterable elements = parameters.parameters
|
||||
.map((FormalParameter parameter) => parameter.element);
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeParameters(elements);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('(int i, String s)'));
|
||||
}
|
||||
|
||||
void test_writeParametersMatchingArguments_named() {
|
||||
String content = '''
|
||||
f(int i, String s) {
|
||||
g(s, index: i);
|
||||
}''';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
FunctionDeclaration f = unit.declarations[0];
|
||||
BlockFunctionBody body = f.functionExpression.body;
|
||||
ExpressionStatement statement = body.block.statements[0];
|
||||
MethodInvocation invocation = statement.expression;
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeParametersMatchingArguments(invocation.argumentList);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(
|
||||
edit.replacement, equalsIgnoringWhitespace('(String s, [int index])'));
|
||||
}
|
||||
|
||||
void test_writeParametersMatchingArguments_required() {
|
||||
String content = '''
|
||||
f(int i, String s) {
|
||||
g(s, i);
|
||||
}''';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
FunctionDeclaration f = unit.declarations[0];
|
||||
BlockFunctionBody body = f.functionExpression.body;
|
||||
ExpressionStatement statement = body.block.statements[0];
|
||||
MethodInvocation invocation = statement.expression;
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeParametersMatchingArguments(invocation.argumentList);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('(String s, int i)'));
|
||||
}
|
||||
|
||||
void test_writeParameterSource() {
|
||||
String content = 'class A {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
ClassDeclaration classA = unit.declarations[0];
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeParameterSource(classA.element.type, 'a');
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('A a'));
|
||||
}
|
||||
|
||||
void test_writeType_dymanic() {
|
||||
String content = 'class A {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeType(unit.element.context.typeProvider.dynamicType);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace(''));
|
||||
}
|
||||
|
||||
void test_writeType_genericType() {
|
||||
String content = 'class A {} class B<E> {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
ClassDeclaration classA = unit.declarations[0];
|
||||
ClassDeclaration classB = unit.declarations[1];
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder
|
||||
.writeType(classB.element.type.substitute4([classA.element.type]));
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('B<A>'));
|
||||
}
|
||||
|
||||
void test_writeType_groupName() {
|
||||
String content = 'class A {} class B extends A {} class C extends B {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
ClassDeclaration classC = unit.declarations[2];
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeType(classC.element.type, groupName: 'type');
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('C'));
|
||||
|
||||
List<LinkedEditGroup> linkedEditGroups =
|
||||
builder.sourceChange.linkedEditGroups;
|
||||
expect(linkedEditGroups, hasLength(1));
|
||||
LinkedEditGroup group = linkedEditGroups[0];
|
||||
expect(group, isNotNull);
|
||||
}
|
||||
|
||||
void test_writeType_groupName_addSupertypeProposals() {
|
||||
String content = 'class A {} class B extends A {} class C extends B {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
ClassDeclaration classC = unit.declarations[2];
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeType(classC.element.type,
|
||||
addSupertypeProposals: true, groupName: 'type');
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('C'));
|
||||
|
||||
List<LinkedEditGroup> linkedEditGroups =
|
||||
builder.sourceChange.linkedEditGroups;
|
||||
expect(linkedEditGroups, hasLength(1));
|
||||
LinkedEditGroup group = linkedEditGroups[0];
|
||||
List<LinkedEditSuggestion> suggestions = group.suggestions;
|
||||
expect(suggestions, hasLength(4));
|
||||
Iterable<String> values = suggestions
|
||||
.map((LinkedEditSuggestion suggestion) {
|
||||
expect(suggestion.kind, LinkedEditSuggestionKind.TYPE);
|
||||
return suggestion.value;
|
||||
});
|
||||
expect(values, contains('Object'));
|
||||
expect(values, contains('A'));
|
||||
expect(values, contains('B'));
|
||||
expect(values, contains('C'));
|
||||
}
|
||||
|
||||
void test_writeType_null() {
|
||||
String content = 'class A {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
resolveLibraryUnit(source);
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeType(null);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace(''));
|
||||
}
|
||||
|
||||
void test_writeType_required_dymanic() {
|
||||
String content = 'class A {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeType(unit.element.context.typeProvider.dynamicType,
|
||||
required: true);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('var'));
|
||||
}
|
||||
|
||||
void test_writeType_required_notNull() {
|
||||
String content = 'class A {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
ClassDeclaration classA = unit.declarations[0];
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeType(classA.element.type, required: true);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('A'));
|
||||
}
|
||||
|
||||
void test_writeType_required_null() {
|
||||
String content = 'class A {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
resolveLibraryUnit(source);
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeType(null, required: true);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('var'));
|
||||
}
|
||||
|
||||
void test_writeType_simpleType() {
|
||||
String content = 'class A {}';
|
||||
Source source = addSource('/test.dart', content);
|
||||
CompilationUnit unit = resolveLibraryUnit(source);
|
||||
ClassDeclaration classA = unit.declarations[0];
|
||||
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, 1, (DartFileEditBuilderImpl builder) {
|
||||
builder.addInsertion(content.length - 1, (DartEditBuilder builder) {
|
||||
builder.writeType(classA.element.type);
|
||||
});
|
||||
});
|
||||
SourceEdit edit = getEdit(builder);
|
||||
expect(edit.replacement, equalsIgnoringWhitespace('A'));
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class DartFileEditBuilderImplTest extends AbstractContextTest {
|
||||
void test_createEditBuilder() {
|
||||
Source source = addSource('/test.dart', 'library test;');
|
||||
resolveLibraryUnit(source);
|
||||
int timeStamp = 65;
|
||||
DartChangeBuilderImpl builder = new DartChangeBuilderImpl(context);
|
||||
builder.addFileEdit(source, timeStamp, (DartFileEditBuilderImpl builder) {
|
||||
int offset = 4;
|
||||
int length = 5;
|
||||
DartEditBuilderImpl editBuilder =
|
||||
builder.createEditBuilder(offset, length);
|
||||
expect(editBuilder, new isInstanceOf<DartEditBuilder>());
|
||||
SourceEdit sourceEdit = editBuilder.sourceEdit;
|
||||
expect(sourceEdit.length, length);
|
||||
expect(sourceEdit.offset, offset);
|
||||
expect(sourceEdit.replacement, isEmpty);
|
||||
});
|
||||
}
|
||||
}
|
17
pkg/analysis_server/test/src/utilities/test_all.dart
Normal file
17
pkg/analysis_server/test/src/utilities/test_all.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) 2014, 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 test.services;
|
||||
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
import 'change_builder_core_test.dart' as change_builder_core_test;
|
||||
import 'change_builder_dart_test.dart' as change_builder_dart_test;
|
||||
|
||||
/// Utility for manually running all tests.
|
||||
main() {
|
||||
groupSep = ' | ';
|
||||
change_builder_core_test.main();
|
||||
change_builder_dart_test.main();
|
||||
}
|
|
@ -20,6 +20,7 @@ import 'search/test_all.dart' as search_all;
|
|||
import 'services/test_all.dart' as services_all;
|
||||
import 'socket_server_test.dart' as socket_server_test;
|
||||
import 'source/test_all.dart' as source_all;
|
||||
import 'src/test_all.dart' as src_all;
|
||||
|
||||
/**
|
||||
* Utility for manually running all tests.
|
||||
|
@ -43,5 +44,6 @@ main() {
|
|||
services_all.main();
|
||||
socket_server_test.main();
|
||||
source_all.main();
|
||||
src_all.main();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue