blob: a89ff0230fe1e4c188c47a6d39ee3defed6e7d92 [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.
library computer.outline;
import 'package:analysis_server/plugin/protocol/protocol.dart';
import 'package:analysis_server/src/collections.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart' as engine;
import 'package:analyzer/dart/element/type.dart' as engine;
import 'package:analyzer/src/generated/source.dart';
/**
* A computer for [CompilationUnit] outline.
*/
class DartUnitOutlineComputer {
final String file;
final CompilationUnit unit;
final LineInfo lineInfo;
DartUnitOutlineComputer(this.file, this.lineInfo, this.unit);
/**
* Returns the computed outline, not `null`.
*/
Outline compute() {
List<Outline> unitContents = <Outline>[];
for (CompilationUnitMember unitMember in unit.declarations) {
if (unitMember is ClassDeclaration) {
ClassDeclaration classDeclaration = unitMember;
List<Outline> classContents = <Outline>[];
for (ClassMember classMember in classDeclaration.members) {
if (classMember is ConstructorDeclaration) {
ConstructorDeclaration constructorDeclaration = classMember;
classContents.add(_newConstructorOutline(constructorDeclaration));
}
if (classMember is FieldDeclaration) {
FieldDeclaration fieldDeclaration = classMember;
VariableDeclarationList fields = fieldDeclaration.fields;
if (fields != null) {
TypeName fieldType = fields.type;
String fieldTypeName = _safeToSource(fieldType);
for (VariableDeclaration field in fields.variables) {
classContents.add(_newVariableOutline(fieldTypeName,
ElementKind.FIELD, field, fieldDeclaration.isStatic));
}
}
}
if (classMember is MethodDeclaration) {
MethodDeclaration methodDeclaration = classMember;
classContents.add(_newMethodOutline(methodDeclaration));
}
}
unitContents.add(_newClassOutline(classDeclaration, classContents));
}
if (unitMember is EnumDeclaration) {
EnumDeclaration enumDeclaration = unitMember;
List<Outline> constantOutlines = <Outline>[];
for (EnumConstantDeclaration constant in enumDeclaration.constants) {
constantOutlines.add(_newEnumConstant(constant));
}
unitContents.add(_newEnumOutline(enumDeclaration, constantOutlines));
}
if (unitMember is TopLevelVariableDeclaration) {
TopLevelVariableDeclaration fieldDeclaration = unitMember;
VariableDeclarationList fields = fieldDeclaration.variables;
if (fields != null) {
TypeName fieldType = fields.type;
String fieldTypeName = _safeToSource(fieldType);
for (VariableDeclaration field in fields.variables) {
unitContents.add(_newVariableOutline(
fieldTypeName, ElementKind.TOP_LEVEL_VARIABLE, field, false));
}
}
}
if (unitMember is FunctionDeclaration) {
FunctionDeclaration functionDeclaration = unitMember;
unitContents.add(_newFunctionOutline(functionDeclaration, true));
}
if (unitMember is ClassTypeAlias) {
ClassTypeAlias alias = unitMember;
unitContents.add(_newClassTypeAlias(alias));
}
if (unitMember is FunctionTypeAlias) {
FunctionTypeAlias alias = unitMember;
unitContents.add(_newFunctionTypeAliasOutline(alias));
}
}
Outline unitOutline = _newUnitOutline(unitContents);
return unitOutline;
}
List<Outline> _addLocalFunctionOutlines(FunctionBody body) {
List<Outline> contents = <Outline>[];
body.accept(new _LocalFunctionOutlinesVisitor(this, contents));
return contents;
}
Location _getLocationNode(AstNode node) {
int offset = node.offset;
int length = node.length;
return _getLocationOffsetLength(offset, length);
}
Location _getLocationOffsetLength(int offset, int length) {
LineInfo_Location lineLocation = lineInfo.getLocation(offset);
int startLine = lineLocation.lineNumber;
int startColumn = lineLocation.columnNumber;
return new Location(file, offset, length, startLine, startColumn);
}
/**
* Returns the [AstNode]'s source region.
*/
_SourceRegion _getSourceRegion(AstNode node) {
int endOffset = node.end;
// prepare position of the node among its siblings
int firstOffset;
List<AstNode> siblings;
AstNode parent = node.parent;
// field
if (parent is VariableDeclarationList) {
VariableDeclarationList variableList = parent as VariableDeclarationList;
List<VariableDeclaration> variables = variableList.variables;
int variableIndex = variables.indexOf(node);
if (variableIndex == variables.length - 1) {
endOffset = variableList.parent.end;
}
if (variableIndex == 0) {
node = parent.parent;
parent = node.parent;
} else if (variableIndex >= 1) {
firstOffset = variables[variableIndex - 1].end;
return new _SourceRegion(firstOffset, endOffset - firstOffset);
}
}
// unit or class member
if (parent is CompilationUnit) {
firstOffset = node.offset;
siblings = parent.declarations;
} else if (parent is ClassDeclaration) {
firstOffset = parent.leftBracket.end;
siblings = parent.members;
} else {
int offset = node.offset;
return new _SourceRegion(offset, endOffset - offset);
}
// first child: [endOfParent, endOfNode]
int index = siblings.indexOf(node);
if (index == 0) {
return new _SourceRegion(firstOffset, endOffset - firstOffset);
}
// not first child: [endOfPreviousSibling, endOfNode]
int prevSiblingEnd = siblings[index - 1].end;
return new _SourceRegion(prevSiblingEnd, endOffset - prevSiblingEnd);
}
Outline _newClassOutline(ClassDeclaration node, List<Outline> classContents) {
SimpleIdentifier nameNode = node.name;
String name = nameNode.name;
_SourceRegion sourceRegion = _getSourceRegion(node);
Element element = new Element(
ElementKind.CLASS,
name,
Element.makeFlags(
isPrivate: Identifier.isPrivateName(name),
isDeprecated: _isDeprecated(node),
isAbstract: node.isAbstract),
location: _getLocationNode(nameNode),
typeParameters: _getTypeParametersStr(node.typeParameters));
return new Outline(element, sourceRegion.offset, sourceRegion.length,
children: nullIfEmpty(classContents));
}
Outline _newClassTypeAlias(ClassTypeAlias node) {
SimpleIdentifier nameNode = node.name;
String name = nameNode.name;
_SourceRegion sourceRegion = _getSourceRegion(node);
Element element = new Element(
ElementKind.CLASS_TYPE_ALIAS,
name,
Element.makeFlags(
isPrivate: Identifier.isPrivateName(name),
isDeprecated: _isDeprecated(node),
isAbstract: node.isAbstract),
location: _getLocationNode(nameNode),
typeParameters: _getTypeParametersStr(node.typeParameters));
return new Outline(element, sourceRegion.offset, sourceRegion.length);
}
Outline _newConstructorOutline(ConstructorDeclaration constructor) {
Identifier returnType = constructor.returnType;
String name = returnType.name;
int offset = returnType.offset;
int length = returnType.length;
SimpleIdentifier constructorNameNode = constructor.name;
bool isPrivate = false;
if (constructorNameNode != null) {
String constructorName = constructorNameNode.name;
isPrivate = Identifier.isPrivateName(constructorName);
name += '.$constructorName';
offset = constructorNameNode.offset;
length = constructorNameNode.length;
}
_SourceRegion sourceRegion = _getSourceRegion(constructor);
FormalParameterList parameters = constructor.parameters;
String parametersStr = _safeToSource(parameters);
Element element = new Element(
ElementKind.CONSTRUCTOR,
name,
Element.makeFlags(
isPrivate: isPrivate, isDeprecated: _isDeprecated(constructor)),
location: _getLocationOffsetLength(offset, length),
parameters: parametersStr);
List<Outline> contents = _addLocalFunctionOutlines(constructor.body);
Outline outline = new Outline(
element, sourceRegion.offset, sourceRegion.length,
children: nullIfEmpty(contents));
return outline;
}
Outline _newEnumConstant(EnumConstantDeclaration node) {
SimpleIdentifier nameNode = node.name;
String name = nameNode.name;
_SourceRegion sourceRegion = _getSourceRegion(node);
Element element = new Element(
ElementKind.ENUM_CONSTANT,
name,
Element.makeFlags(
isPrivate: Identifier.isPrivateName(name),
isDeprecated: _isDeprecated(node)),
location: _getLocationNode(nameNode));
return new Outline(element, sourceRegion.offset, sourceRegion.length);
}
Outline _newEnumOutline(EnumDeclaration node, List<Outline> children) {
SimpleIdentifier nameNode = node.name;
String name = nameNode.name;
_SourceRegion sourceRegion = _getSourceRegion(node);
Element element = new Element(
ElementKind.ENUM,
name,
Element.makeFlags(
isPrivate: Identifier.isPrivateName(name),
isDeprecated: _isDeprecated(node)),
location: _getLocationNode(nameNode));
return new Outline(element, sourceRegion.offset, sourceRegion.length,
children: nullIfEmpty(children));
}
Outline _newFunctionOutline(FunctionDeclaration function, bool isStatic) {
TypeName returnType = function.returnType;
SimpleIdentifier nameNode = function.name;
String name = nameNode.name;
FunctionExpression functionExpression = function.functionExpression;
FormalParameterList parameters = functionExpression.parameters;
ElementKind kind;
if (function.isGetter) {
kind = ElementKind.GETTER;
} else if (function.isSetter) {
kind = ElementKind.SETTER;
} else {
kind = ElementKind.FUNCTION;
}
_SourceRegion sourceRegion = _getSourceRegion(function);
String parametersStr = _safeToSource(parameters);
String returnTypeStr = _safeToSource(returnType);
Element element = new Element(
kind,
name,
Element.makeFlags(
isPrivate: Identifier.isPrivateName(name),
isDeprecated: _isDeprecated(function),
isStatic: isStatic),
location: _getLocationNode(nameNode),
parameters: parametersStr,
returnType: returnTypeStr);
List<Outline> contents = _addLocalFunctionOutlines(functionExpression.body);
Outline outline = new Outline(
element, sourceRegion.offset, sourceRegion.length,
children: nullIfEmpty(contents));
return outline;
}
Outline _newFunctionTypeAliasOutline(FunctionTypeAlias node) {
TypeName returnType = node.returnType;
SimpleIdentifier nameNode = node.name;
String name = nameNode.name;
_SourceRegion sourceRegion = _getSourceRegion(node);
FormalParameterList parameters = node.parameters;
String parametersStr = _safeToSource(parameters);
String returnTypeStr = _safeToSource(returnType);
Element element = new Element(
ElementKind.FUNCTION_TYPE_ALIAS,
name,
Element.makeFlags(
isPrivate: Identifier.isPrivateName(name),
isDeprecated: _isDeprecated(node)),
location: _getLocationNode(nameNode),
parameters: parametersStr,
returnType: returnTypeStr,
typeParameters: _getTypeParametersStr(node.typeParameters));
return new Outline(element, sourceRegion.offset, sourceRegion.length);
}
Outline _newMethodOutline(MethodDeclaration method) {
TypeName returnType = method.returnType;
SimpleIdentifier nameNode = method.name;
String name = nameNode.name;
FormalParameterList parameters = method.parameters;
ElementKind kind;
if (method.isGetter) {
kind = ElementKind.GETTER;
} else if (method.isSetter) {
kind = ElementKind.SETTER;
} else {
kind = ElementKind.METHOD;
}
_SourceRegion sourceRegion = _getSourceRegion(method);
String parametersStr = parameters?.toSource();
String returnTypeStr = _safeToSource(returnType);
Element element = new Element(
kind,
name,
Element.makeFlags(
isPrivate: Identifier.isPrivateName(name),
isDeprecated: _isDeprecated(method),
isAbstract: method.isAbstract,
isStatic: method.isStatic),
location: _getLocationNode(nameNode),
parameters: parametersStr,
returnType: returnTypeStr);
List<Outline> contents = _addLocalFunctionOutlines(method.body);
Outline outline = new Outline(
element, sourceRegion.offset, sourceRegion.length,
children: nullIfEmpty(contents));
return outline;
}
Outline _newUnitOutline(List<Outline> unitContents) {
Element element = new Element(
ElementKind.COMPILATION_UNIT, '<unit>', Element.makeFlags(),
location: _getLocationNode(unit));
return new Outline(element, unit.offset, unit.length,
children: nullIfEmpty(unitContents));
}
Outline _newVariableOutline(String typeName, ElementKind kind,
VariableDeclaration variable, bool isStatic) {
SimpleIdentifier nameNode = variable.name;
String name = nameNode.name;
_SourceRegion sourceRegion = _getSourceRegion(variable);
Element element = new Element(
kind,
name,
Element.makeFlags(
isPrivate: Identifier.isPrivateName(name),
isDeprecated: _isDeprecated(variable),
isStatic: isStatic,
isConst: variable.isConst,
isFinal: variable.isFinal),
location: _getLocationNode(nameNode),
returnType: typeName);
Outline outline =
new Outline(element, sourceRegion.offset, sourceRegion.length);
return outline;
}
static String _getTypeParametersStr(TypeParameterList parameters) {
if (parameters == null) {
return null;
}
return parameters.toSource();
}
/**
* Returns `true` if the given [element] is not `null` and deprecated.
*/
static bool _isDeprecated(Declaration declaration) {
engine.Element element = declaration.element;
return element != null && element.isDeprecated;
}
static String _safeToSource(AstNode node) =>
node == null ? '' : node.toSource();
}
/**
* A visitor for building local function outlines.
*/
class _LocalFunctionOutlinesVisitor extends RecursiveAstVisitor {
final DartUnitOutlineComputer outlineComputer;
final List<Outline> contents;
_LocalFunctionOutlinesVisitor(this.outlineComputer, this.contents);
@override
visitFunctionDeclaration(FunctionDeclaration node) {
contents.add(outlineComputer._newFunctionOutline(node, false));
}
}
/**
* A range of characters.
*/
class _SourceRegion {
final int length;
final int offset;
_SourceRegion(this.offset, this.length);
}