blob: 990824406a49b1157fb976327e2d71507045fed5 [file] [log] [blame]
// 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.
import 'package:analysis_server/src/services/correction/organize_imports.dart';
import 'package:analysis_server/src/utilities/extensions/range_factory.dart';
import 'package:analysis_server/src/utilities/strings.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
import 'package:analyzer_plugin/utilities/range_factory.dart';
/// Sorter for unit/class members.
class MemberSorter {
static final List<_PriorityItem> _PRIORITY_ITEMS = [
_PriorityItem(false, _MemberKind.UNIT_FUNCTION_MAIN, false),
_PriorityItem(false, _MemberKind.UNIT_VARIABLE_CONST, false),
_PriorityItem(false, _MemberKind.UNIT_VARIABLE_CONST, true),
_PriorityItem(false, _MemberKind.UNIT_VARIABLE, false),
_PriorityItem(false, _MemberKind.UNIT_VARIABLE, true),
_PriorityItem(false, _MemberKind.UNIT_ACCESSOR, false),
_PriorityItem(false, _MemberKind.UNIT_ACCESSOR, true),
_PriorityItem(false, _MemberKind.UNIT_FUNCTION, false),
_PriorityItem(false, _MemberKind.UNIT_FUNCTION, true),
_PriorityItem(false, _MemberKind.UNIT_GENERIC_TYPE_ALIAS, false),
_PriorityItem(false, _MemberKind.UNIT_GENERIC_TYPE_ALIAS, true),
_PriorityItem(false, _MemberKind.UNIT_FUNCTION_TYPE, false),
_PriorityItem(false, _MemberKind.UNIT_FUNCTION_TYPE, true),
_PriorityItem(false, _MemberKind.UNIT_CLASS, false),
_PriorityItem(false, _MemberKind.UNIT_CLASS, true),
_PriorityItem(false, _MemberKind.UNIT_EXTENSION, false),
_PriorityItem(false, _MemberKind.UNIT_EXTENSION, true),
_PriorityItem(true, _MemberKind.CLASS_FIELD, false),
_PriorityItem(true, _MemberKind.CLASS_ACCESSOR, false),
_PriorityItem(true, _MemberKind.CLASS_ACCESSOR, true),
_PriorityItem(false, _MemberKind.CLASS_FIELD, false),
_PriorityItem(false, _MemberKind.CLASS_CONSTRUCTOR, false),
_PriorityItem(false, _MemberKind.CLASS_CONSTRUCTOR, true),
_PriorityItem(false, _MemberKind.CLASS_ACCESSOR, false),
_PriorityItem(false, _MemberKind.CLASS_ACCESSOR, true),
_PriorityItem(false, _MemberKind.CLASS_METHOD, false),
_PriorityItem(false, _MemberKind.CLASS_METHOD, true),
_PriorityItem(true, _MemberKind.CLASS_METHOD, false),
_PriorityItem(true, _MemberKind.CLASS_METHOD, true)
];
final String initialCode;
final CompilationUnit unit;
final LineInfo lineInfo;
String code;
String endOfLine = '\n';
MemberSorter(this.initialCode, this.unit, this.lineInfo)
: code = initialCode {
endOfLine = getEOL(code);
}
/// Return the [SourceEdit]s that sort [unit].
List<SourceEdit> sort() {
_sortClassesMembers();
_sortUnitMembers();
// Must sort unit directives last because it may insert newlines, which
// would confuse the offsets used by the other sort functions.
_sortUnitDirectives();
// prepare edits
var edits = <SourceEdit>[];
if (code != initialCode) {
var diff = computeSimpleDiff(initialCode, code);
var edit = SourceEdit(diff.offset, diff.length, diff.replacement);
edits.add(edit);
}
return edits;
}
void _sortAndReorderMembers(List<_MemberInfo> members) {
var membersSorted = _getSortedMembers(members);
var size = membersSorted.length;
for (var i = 0; i < size; i++) {
var newInfo = membersSorted[size - 1 - i];
var oldInfo = members[size - 1 - i];
if (newInfo != oldInfo) {
var beforeCode = code.substring(0, oldInfo.offset);
var afterCode = code.substring(oldInfo.end);
code = beforeCode + newInfo.text + afterCode;
}
}
}
/// Sorts all members of all [ClassOrMixinDeclaration]s.
void _sortClassesMembers() {
for (var unitMember in unit.declarations) {
if (unitMember is ClassOrMixinDeclaration) {
_sortClassMembers(unitMember);
}
}
}
/// Sorts all members of the given [classDeclaration].
void _sortClassMembers(ClassOrMixinDeclaration classDeclaration) {
var members = <_MemberInfo>[];
for (var member in classDeclaration.members) {
_MemberKind kind;
var isStatic = false;
String name;
if (member is ConstructorDeclaration) {
kind = _MemberKind.CLASS_CONSTRUCTOR;
var nameNode = member.name;
if (nameNode == null) {
name = '';
} else {
name = nameNode.name;
}
} else if (member is FieldDeclaration) {
var fieldDeclaration = member;
List<VariableDeclaration> fields = fieldDeclaration.fields.variables;
if (fields.isNotEmpty) {
kind = _MemberKind.CLASS_FIELD;
isStatic = fieldDeclaration.isStatic;
name = fields[0].name.name;
} else {
// Don't sort members if there are errors in the code.
return;
}
} else if (member is MethodDeclaration) {
var method = member;
isStatic = method.isStatic;
name = method.name.name;
if (method.isGetter) {
kind = _MemberKind.CLASS_ACCESSOR;
name += ' getter';
} else if (method.isSetter) {
kind = _MemberKind.CLASS_ACCESSOR;
name += ' setter';
} else {
kind = _MemberKind.CLASS_METHOD;
}
} else {
throw StateError('Unsupported class of member: ${member.runtimeType}');
}
var item = _PriorityItem.forName(isStatic, name, kind);
var nodeRange = range.nodeWithComments(lineInfo, member);
var offset = nodeRange.offset;
var length = nodeRange.length;
var text = code.substring(offset, offset + length);
members.add(_MemberInfo(item, name, offset, length, text));
}
// do sort
_sortAndReorderMembers(members);
}
/// Sorts all [Directive]s.
void _sortUnitDirectives() {
final importOrganizer =
ImportOrganizer(code, unit, [], removeUnused: false);
importOrganizer.organize();
code = importOrganizer.code;
}
/// Sorts all [CompilationUnitMember]s.
void _sortUnitMembers() {
var members = <_MemberInfo>[];
for (var member in unit.declarations) {
_MemberKind kind;
String name;
if (member is ClassOrMixinDeclaration) {
kind = _MemberKind.UNIT_CLASS;
name = member.name.name;
} else if (member is ClassTypeAlias) {
kind = _MemberKind.UNIT_CLASS;
name = member.name.name;
} else if (member is EnumDeclaration) {
kind = _MemberKind.UNIT_CLASS;
name = member.name.name;
} else if (member is ExtensionDeclaration) {
kind = _MemberKind.UNIT_EXTENSION;
name = member.name?.name ?? '';
} else if (member is FunctionDeclaration) {
var function = member;
name = function.name.name;
if (function.isGetter) {
kind = _MemberKind.UNIT_ACCESSOR;
name += ' getter';
} else if (function.isSetter) {
kind = _MemberKind.UNIT_ACCESSOR;
name += ' setter';
} else {
if (name == 'main') {
kind = _MemberKind.UNIT_FUNCTION_MAIN;
} else {
kind = _MemberKind.UNIT_FUNCTION;
}
}
} else if (member is FunctionTypeAlias) {
kind = _MemberKind.UNIT_FUNCTION_TYPE;
name = member.name.name;
} else if (member is GenericTypeAlias) {
kind = _MemberKind.UNIT_GENERIC_TYPE_ALIAS;
name = member.name.name;
} else if (member is TopLevelVariableDeclaration) {
var variableDeclaration = member;
List<VariableDeclaration> variables =
variableDeclaration.variables.variables;
if (variables.isNotEmpty) {
if (variableDeclaration.variables.isConst) {
kind = _MemberKind.UNIT_VARIABLE_CONST;
} else {
kind = _MemberKind.UNIT_VARIABLE;
}
name = variables[0].name.name;
} else {
// Don't sort members if there are errors in the code.
return;
}
} else {
throw StateError('Unsupported class of member: ${member.runtimeType}');
}
var item = _PriorityItem.forName(false, name, kind);
var nodeRange = range.nodeWithComments(lineInfo, member);
var offset = nodeRange.offset;
var length = nodeRange.length;
var text = code.substring(offset, offset + length);
members.add(_MemberInfo(item, name, offset, length, text));
}
// do sort
_sortAndReorderMembers(members);
}
/// Return the EOL to use for [code].
static String getEOL(String code) {
if (code.contains('\r\n')) {
return '\r\n';
} else {
return '\n';
}
}
static int _getPriority(_PriorityItem item) {
for (var i = 0; i < _PRIORITY_ITEMS.length; i++) {
if (_PRIORITY_ITEMS[i] == item) {
return i;
}
}
return 0;
}
static List<_MemberInfo> _getSortedMembers(List<_MemberInfo> members) {
var membersSorted = List<_MemberInfo>.from(members);
membersSorted.sort((_MemberInfo o1, _MemberInfo o2) {
var priority1 = _getPriority(o1.item);
var priority2 = _getPriority(o2.item);
if (priority1 == priority2) {
// don't reorder class fields
if (o1.item.kind == _MemberKind.CLASS_FIELD) {
return o1.offset - o2.offset;
}
// sort all other members by name
var name1 = o1.name.toLowerCase();
var name2 = o2.name.toLowerCase();
return name1.compareTo(name2);
}
return priority1 - priority2;
});
return membersSorted;
}
}
class _MemberInfo {
final _PriorityItem item;
final String name;
final int offset;
final int length;
final int end;
final String text;
_MemberInfo(this.item, this.name, this.offset, this.length, this.text)
: end = offset + length;
@override
String toString() {
return '(priority=$item; name=$name; offset=$offset; length=$length)';
}
}
class _MemberKind {
static const CLASS_ACCESSOR = _MemberKind('CLASS_ACCESSOR');
static const CLASS_CONSTRUCTOR = _MemberKind('CLASS_CONSTRUCTOR');
static const CLASS_FIELD = _MemberKind('CLASS_FIELD');
static const CLASS_METHOD = _MemberKind('CLASS_METHOD');
static const UNIT_ACCESSOR = _MemberKind('UNIT_ACCESSOR');
static const UNIT_CLASS = _MemberKind('UNIT_CLASS');
static const UNIT_EXTENSION = _MemberKind('UNIT_EXTENSION');
static const UNIT_FUNCTION = _MemberKind('UNIT_FUNCTION');
static const UNIT_FUNCTION_MAIN = _MemberKind('UNIT_FUNCTION_MAIN');
static const UNIT_FUNCTION_TYPE = _MemberKind('UNIT_FUNCTION_TYPE');
static const UNIT_GENERIC_TYPE_ALIAS = _MemberKind('UNIT_GENERIC_TYPE_ALIAS');
static const UNIT_VARIABLE = _MemberKind('UNIT_VARIABLE');
static const UNIT_VARIABLE_CONST = _MemberKind('UNIT_VARIABLE_CONST');
final String name;
const _MemberKind(this.name);
@override
String toString() => name;
}
class _PriorityItem {
final _MemberKind kind;
final bool isPrivate;
final bool isStatic;
_PriorityItem(this.isStatic, this.kind, this.isPrivate);
factory _PriorityItem.forName(bool isStatic, String name, _MemberKind kind) {
var isPrivate = Identifier.isPrivateName(name);
return _PriorityItem(isStatic, kind, isPrivate);
}
@override
bool operator ==(Object obj) {
var other = obj as _PriorityItem;
if (kind == _MemberKind.CLASS_FIELD) {
return other.kind == kind && other.isStatic == isStatic;
}
return other.kind == kind &&
other.isPrivate == isPrivate &&
other.isStatic == isStatic;
}
@override
String toString() => kind.toString();
}