e97f1bdbf0
This should allow doing partial migration, specifically protocol files, which are imported by other libraries, but are a small library cycle that does not import much outside of it. Change-Id: I904c05d6d5b444ee9a9dbd1f7ada12aabdcc5165 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193583 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
527 lines
18 KiB
Dart
527 lines
18 KiB
Dart
// 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.
|
|
|
|
// @dart = 2.9
|
|
|
|
import 'package:analysis_server/src/collections.dart';
|
|
import 'package:analysis_server/src/utilities/flutter.dart';
|
|
import 'package:analyzer/dart/analysis/results.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_plugin/protocol/protocol_common.dart';
|
|
|
|
/// A computer for [CompilationUnit] outline.
|
|
class DartUnitOutlineComputer {
|
|
final ResolvedUnitResult resolvedUnit;
|
|
final bool withBasicFlutter;
|
|
|
|
DartUnitOutlineComputer(this.resolvedUnit, {this.withBasicFlutter = false});
|
|
|
|
/// Returns the computed outline, not `null`.
|
|
Outline compute() {
|
|
var unitContents = <Outline>[];
|
|
for (var unitMember in resolvedUnit.unit.declarations) {
|
|
if (unitMember is ClassDeclaration) {
|
|
unitContents.add(_newClassOutline(
|
|
unitMember, _outlinesForMembers(unitMember.members)));
|
|
} else if (unitMember is MixinDeclaration) {
|
|
unitContents.add(_newMixinOutline(
|
|
unitMember, _outlinesForMembers(unitMember.members)));
|
|
} else if (unitMember is EnumDeclaration) {
|
|
var enumDeclaration = unitMember;
|
|
var constantOutlines = <Outline>[];
|
|
for (var constant in enumDeclaration.constants) {
|
|
constantOutlines.add(_newEnumConstant(constant));
|
|
}
|
|
unitContents.add(_newEnumOutline(enumDeclaration, constantOutlines));
|
|
} else if (unitMember is ExtensionDeclaration) {
|
|
unitContents.add(_newExtensionOutline(
|
|
unitMember, _outlinesForMembers(unitMember.members)));
|
|
} else if (unitMember is TopLevelVariableDeclaration) {
|
|
var fieldDeclaration = unitMember;
|
|
var fields = fieldDeclaration.variables;
|
|
if (fields != null) {
|
|
var fieldType = fields.type;
|
|
var fieldTypeName = _safeToSource(fieldType);
|
|
for (var field in fields.variables) {
|
|
unitContents.add(_newVariableOutline(
|
|
fieldTypeName, ElementKind.TOP_LEVEL_VARIABLE, field, false));
|
|
}
|
|
}
|
|
} else if (unitMember is FunctionDeclaration) {
|
|
var functionDeclaration = unitMember;
|
|
unitContents.add(_newFunctionOutline(functionDeclaration, true));
|
|
} else if (unitMember is ClassTypeAlias) {
|
|
var alias = unitMember;
|
|
unitContents.add(_newClassTypeAlias(alias));
|
|
} else if (unitMember is FunctionTypeAlias) {
|
|
var alias = unitMember;
|
|
unitContents.add(_newFunctionTypeAliasOutline(alias));
|
|
} else if (unitMember is GenericTypeAlias) {
|
|
var alias = unitMember;
|
|
unitContents.add(_newGenericTypeAliasOutline(alias));
|
|
}
|
|
}
|
|
var unitOutline = _newUnitOutline(unitContents);
|
|
return unitOutline;
|
|
}
|
|
|
|
List<Outline> _addFunctionBodyOutlines(FunctionBody body) {
|
|
var contents = <Outline>[];
|
|
body.accept(_FunctionBodyOutlinesVisitor(this, contents));
|
|
return contents;
|
|
}
|
|
|
|
Location _getLocationNode(AstNode node) {
|
|
var offset = node.offset;
|
|
var length = node.length;
|
|
return _getLocationOffsetLength(offset, length);
|
|
}
|
|
|
|
Location _getLocationOffsetLength(int offset, int length) {
|
|
var startLocation = resolvedUnit.lineInfo.getLocation(offset);
|
|
var startLine = startLocation.lineNumber;
|
|
var startColumn = startLocation.columnNumber;
|
|
var endLocation = resolvedUnit.lineInfo.getLocation(offset + length);
|
|
var endLine = endLocation.lineNumber;
|
|
var endColumn = endLocation.columnNumber;
|
|
return Location(resolvedUnit.path, offset, length, startLine, startColumn,
|
|
endLine, endColumn);
|
|
}
|
|
|
|
Outline _newClassOutline(ClassDeclaration node, List<Outline> classContents) {
|
|
var nameNode = node.name;
|
|
var name = nameNode.name;
|
|
var element = Element(
|
|
ElementKind.CLASS,
|
|
name,
|
|
Element.makeFlags(
|
|
isPrivate: Identifier.isPrivateName(name),
|
|
isDeprecated: _isDeprecated(node),
|
|
isAbstract: node.isAbstract),
|
|
location: _getLocationNode(nameNode),
|
|
typeParameters: _getTypeParametersStr(node.typeParameters));
|
|
return _nodeOutline(node, element, classContents);
|
|
}
|
|
|
|
Outline _newClassTypeAlias(ClassTypeAlias node) {
|
|
var nameNode = node.name;
|
|
var name = nameNode.name;
|
|
var element = 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 _nodeOutline(node, element);
|
|
}
|
|
|
|
Outline _newConstructorOutline(ConstructorDeclaration constructor) {
|
|
var returnType = constructor.returnType;
|
|
var name = returnType.name;
|
|
var offset = returnType.offset;
|
|
var length = returnType.length;
|
|
var constructorNameNode = constructor.name;
|
|
var isPrivate = false;
|
|
if (constructorNameNode != null) {
|
|
var constructorName = constructorNameNode.name;
|
|
isPrivate = Identifier.isPrivateName(constructorName);
|
|
name += '.$constructorName';
|
|
offset = constructorNameNode.offset;
|
|
length = constructorNameNode.length;
|
|
}
|
|
var parameters = constructor.parameters;
|
|
var parametersStr = _safeToSource(parameters);
|
|
var element = Element(
|
|
ElementKind.CONSTRUCTOR,
|
|
name,
|
|
Element.makeFlags(
|
|
isPrivate: isPrivate, isDeprecated: _isDeprecated(constructor)),
|
|
location: _getLocationOffsetLength(offset, length),
|
|
parameters: parametersStr);
|
|
var contents = _addFunctionBodyOutlines(constructor.body);
|
|
return _nodeOutline(constructor, element, contents);
|
|
}
|
|
|
|
Outline _newEnumConstant(EnumConstantDeclaration node) {
|
|
var nameNode = node.name;
|
|
var name = nameNode.name;
|
|
var element = Element(
|
|
ElementKind.ENUM_CONSTANT,
|
|
name,
|
|
Element.makeFlags(
|
|
isPrivate: Identifier.isPrivateName(name),
|
|
isDeprecated: _isDeprecated(node)),
|
|
location: _getLocationNode(nameNode));
|
|
return _nodeOutline(node, element);
|
|
}
|
|
|
|
Outline _newEnumOutline(EnumDeclaration node, List<Outline> children) {
|
|
var nameNode = node.name;
|
|
var name = nameNode.name;
|
|
var element = Element(
|
|
ElementKind.ENUM,
|
|
name,
|
|
Element.makeFlags(
|
|
isPrivate: Identifier.isPrivateName(name),
|
|
isDeprecated: _isDeprecated(node)),
|
|
location: _getLocationNode(nameNode));
|
|
return _nodeOutline(node, element, children);
|
|
}
|
|
|
|
Outline _newExtensionOutline(
|
|
ExtensionDeclaration node, List<Outline> extensionContents) {
|
|
var nameNode = node.name;
|
|
var name = nameNode?.name ?? '';
|
|
var element = Element(
|
|
ElementKind.EXTENSION,
|
|
name,
|
|
Element.makeFlags(
|
|
isPrivate: Identifier.isPrivateName(name),
|
|
isDeprecated: _isDeprecated(node)),
|
|
location: _getLocationNode(nameNode ?? node.extendedType),
|
|
typeParameters: _getTypeParametersStr(node.typeParameters));
|
|
return _nodeOutline(node, element, extensionContents);
|
|
}
|
|
|
|
Outline _newFunctionOutline(FunctionDeclaration function, bool isStatic) {
|
|
var returnType = function.returnType;
|
|
var nameNode = function.name;
|
|
var name = nameNode.name;
|
|
var functionExpression = function.functionExpression;
|
|
var parameters = functionExpression.parameters;
|
|
ElementKind kind;
|
|
if (function.isGetter) {
|
|
kind = ElementKind.GETTER;
|
|
} else if (function.isSetter) {
|
|
kind = ElementKind.SETTER;
|
|
} else {
|
|
kind = ElementKind.FUNCTION;
|
|
}
|
|
var parametersStr = _safeToSource(parameters);
|
|
var returnTypeStr = _safeToSource(returnType);
|
|
var element = Element(
|
|
kind,
|
|
name,
|
|
Element.makeFlags(
|
|
isPrivate: Identifier.isPrivateName(name),
|
|
isDeprecated: _isDeprecated(function),
|
|
isStatic: isStatic),
|
|
location: _getLocationNode(nameNode),
|
|
parameters: parametersStr,
|
|
returnType: returnTypeStr,
|
|
typeParameters:
|
|
_getTypeParametersStr(functionExpression.typeParameters));
|
|
var contents = _addFunctionBodyOutlines(functionExpression.body);
|
|
return _nodeOutline(function, element, contents);
|
|
}
|
|
|
|
Outline _newFunctionTypeAliasOutline(FunctionTypeAlias node) {
|
|
var returnType = node.returnType;
|
|
var nameNode = node.name;
|
|
var name = nameNode.name;
|
|
var parameters = node.parameters;
|
|
var parametersStr = _safeToSource(parameters);
|
|
var returnTypeStr = _safeToSource(returnType);
|
|
var element = 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 _nodeOutline(node, element);
|
|
}
|
|
|
|
Outline _newGenericTypeAliasOutline(GenericTypeAlias node) {
|
|
var nameNode = node.name;
|
|
var name = nameNode.name;
|
|
|
|
var aliasedType = node.type;
|
|
var aliasedFunctionType =
|
|
aliasedType is GenericFunctionType ? aliasedType : null;
|
|
|
|
var element = Element(
|
|
aliasedFunctionType != null
|
|
? ElementKind.FUNCTION_TYPE_ALIAS
|
|
: ElementKind.TYPE_ALIAS,
|
|
name,
|
|
Element.makeFlags(
|
|
isPrivate: Identifier.isPrivateName(name),
|
|
isDeprecated: _isDeprecated(node),
|
|
),
|
|
aliasedType: _safeToSource(aliasedType),
|
|
location: _getLocationNode(nameNode),
|
|
parameters: aliasedFunctionType != null
|
|
? _safeToSource(aliasedFunctionType.parameters)
|
|
: null,
|
|
returnType: aliasedFunctionType != null
|
|
? _safeToSource(aliasedFunctionType.returnType)
|
|
: null,
|
|
typeParameters: _getTypeParametersStr(node.typeParameters),
|
|
);
|
|
|
|
return _nodeOutline(node, element);
|
|
}
|
|
|
|
Outline _newMethodOutline(MethodDeclaration method) {
|
|
var returnType = method.returnType;
|
|
var nameNode = method.name;
|
|
var name = nameNode.name;
|
|
var parameters = method.parameters;
|
|
ElementKind kind;
|
|
if (method.isGetter) {
|
|
kind = ElementKind.GETTER;
|
|
} else if (method.isSetter) {
|
|
kind = ElementKind.SETTER;
|
|
} else {
|
|
kind = ElementKind.METHOD;
|
|
}
|
|
var parametersStr = parameters?.toSource();
|
|
var returnTypeStr = _safeToSource(returnType);
|
|
var element = 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,
|
|
typeParameters: _getTypeParametersStr(method.typeParameters));
|
|
var contents = _addFunctionBodyOutlines(method.body);
|
|
return _nodeOutline(method, element, contents);
|
|
}
|
|
|
|
Outline _newMixinOutline(MixinDeclaration node, List<Outline> mixinContents) {
|
|
node.firstTokenAfterCommentAndMetadata;
|
|
var nameNode = node.name;
|
|
var name = nameNode.name;
|
|
var element = Element(
|
|
ElementKind.MIXIN,
|
|
name,
|
|
Element.makeFlags(
|
|
isPrivate: Identifier.isPrivateName(name),
|
|
isDeprecated: _isDeprecated(node)),
|
|
location: _getLocationNode(nameNode),
|
|
typeParameters: _getTypeParametersStr(node.typeParameters));
|
|
return _nodeOutline(node, element, mixinContents);
|
|
}
|
|
|
|
Outline _newUnitOutline(List<Outline> unitContents) {
|
|
var element = Element(
|
|
ElementKind.COMPILATION_UNIT, '<unit>', Element.makeFlags(),
|
|
location: _getLocationNode(resolvedUnit.unit));
|
|
return _nodeOutline(resolvedUnit.unit, element, unitContents);
|
|
}
|
|
|
|
Outline _newVariableOutline(String typeName, ElementKind kind,
|
|
VariableDeclaration variable, bool isStatic) {
|
|
var nameNode = variable.name;
|
|
var name = nameNode.name;
|
|
var element = 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);
|
|
return _nodeOutline(variable, element);
|
|
}
|
|
|
|
Outline _nodeOutline(AstNode node, Element element,
|
|
[List<Outline> children]) {
|
|
var offset = node.offset;
|
|
var end = node.end;
|
|
if (node is VariableDeclaration) {
|
|
var parent = node.parent;
|
|
if (parent is VariableDeclarationList && parent.variables.isNotEmpty) {
|
|
if (parent.variables[0] == node) {
|
|
offset = parent.parent.offset;
|
|
}
|
|
if (parent.variables.last == node) {
|
|
end = parent.parent.end;
|
|
}
|
|
}
|
|
}
|
|
|
|
var codeOffset = node.offset;
|
|
if (node is AnnotatedNode) {
|
|
codeOffset = node.firstTokenAfterCommentAndMetadata.offset;
|
|
}
|
|
|
|
var length = end - offset;
|
|
var codeLength = node.end - codeOffset;
|
|
return Outline(element, offset, length, codeOffset, codeLength,
|
|
children: nullIfEmpty(children));
|
|
}
|
|
|
|
List<Outline> _outlinesForMembers(List<ClassMember> members) {
|
|
var memberOutlines = <Outline>[];
|
|
for (var classMember in members) {
|
|
if (classMember is ConstructorDeclaration) {
|
|
var constructorDeclaration = classMember;
|
|
memberOutlines.add(_newConstructorOutline(constructorDeclaration));
|
|
}
|
|
if (classMember is FieldDeclaration) {
|
|
var fieldDeclaration = classMember;
|
|
var fields = fieldDeclaration.fields;
|
|
if (fields != null) {
|
|
var fieldType = fields.type;
|
|
var fieldTypeName = _safeToSource(fieldType);
|
|
for (var field in fields.variables) {
|
|
memberOutlines.add(_newVariableOutline(fieldTypeName,
|
|
ElementKind.FIELD, field, fieldDeclaration.isStatic));
|
|
}
|
|
}
|
|
}
|
|
if (classMember is MethodDeclaration) {
|
|
var methodDeclaration = classMember;
|
|
memberOutlines.add(_newMethodOutline(methodDeclaration));
|
|
}
|
|
}
|
|
return memberOutlines;
|
|
}
|
|
|
|
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) {
|
|
var element = declaration.declaredElement;
|
|
return element != null && element.hasDeprecated;
|
|
}
|
|
|
|
static String _safeToSource(AstNode node) =>
|
|
node == null ? '' : node.toSource();
|
|
}
|
|
|
|
/// A visitor for building local function outlines.
|
|
class _FunctionBodyOutlinesVisitor extends RecursiveAstVisitor<void> {
|
|
final DartUnitOutlineComputer outlineComputer;
|
|
final List<Outline> contents;
|
|
|
|
_FunctionBodyOutlinesVisitor(this.outlineComputer, this.contents);
|
|
|
|
Flutter get _flutter => Flutter.instance;
|
|
|
|
/// Return `true` if the given [element] is the method 'group' defined in the
|
|
/// test package.
|
|
bool isGroup(engine.ExecutableElement element) {
|
|
if (element != null && element.hasIsTestGroup) {
|
|
return true;
|
|
}
|
|
return element is engine.FunctionElement &&
|
|
element.name == 'group' &&
|
|
_isInsideTestPackage(element);
|
|
}
|
|
|
|
/// Return `true` if the given [element] is the method 'test' defined in the
|
|
/// test package.
|
|
bool isTest(engine.ExecutableElement element) {
|
|
if (element != null && element.hasIsTest) {
|
|
return true;
|
|
}
|
|
return element is engine.FunctionElement &&
|
|
element.name == 'test' &&
|
|
_isInsideTestPackage(element);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionDeclaration(FunctionDeclaration node) {
|
|
contents.add(outlineComputer._newFunctionOutline(node, false));
|
|
}
|
|
|
|
@override
|
|
void visitInstanceCreationExpression(InstanceCreationExpression node) {
|
|
if (outlineComputer.withBasicFlutter && _flutter.isWidgetCreation(node)) {
|
|
var children = <Outline>[];
|
|
node.argumentList
|
|
.accept(_FunctionBodyOutlinesVisitor(outlineComputer, children));
|
|
|
|
var text = _flutter.getWidgetPresentationText(node);
|
|
var element = Element(ElementKind.CONSTRUCTOR_INVOCATION, text, 0,
|
|
location: outlineComputer._getLocationOffsetLength(node.offset, 0));
|
|
|
|
contents.add(Outline(
|
|
element, node.offset, node.length, node.offset, node.length,
|
|
children: nullIfEmpty(children)));
|
|
} else {
|
|
super.visitInstanceCreationExpression(node);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitMethodInvocation(MethodInvocation node) {
|
|
var nameNode = node.methodName;
|
|
|
|
var nameElement = nameNode.staticElement;
|
|
if (nameElement is! engine.ExecutableElement) {
|
|
return;
|
|
}
|
|
engine.ExecutableElement executableElement = nameElement;
|
|
|
|
String extractString(NodeList<Expression> arguments) {
|
|
if (arguments != null && arguments.isNotEmpty) {
|
|
var argument = arguments[0];
|
|
if (argument is StringLiteral) {
|
|
var value = argument.stringValue;
|
|
if (value != null) {
|
|
return value;
|
|
}
|
|
}
|
|
return argument.toSource();
|
|
}
|
|
return 'unnamed';
|
|
}
|
|
|
|
void addOutlineNode(ElementKind kind, [List<Outline> children]) {
|
|
var executableName = nameNode.name;
|
|
var description = extractString(node.argumentList?.arguments);
|
|
var name = '$executableName("$description")';
|
|
var element = Element(kind, name, 0,
|
|
location: outlineComputer._getLocationNode(nameNode));
|
|
contents.add(Outline(
|
|
element, node.offset, node.length, node.offset, node.length,
|
|
children: nullIfEmpty(children)));
|
|
}
|
|
|
|
if (isGroup(executableElement)) {
|
|
var groupContents = <Outline>[];
|
|
node.argumentList
|
|
.accept(_FunctionBodyOutlinesVisitor(outlineComputer, groupContents));
|
|
addOutlineNode(ElementKind.UNIT_TEST_GROUP, groupContents);
|
|
} else if (isTest(executableElement)) {
|
|
addOutlineNode(ElementKind.UNIT_TEST_TEST);
|
|
} else {
|
|
super.visitMethodInvocation(node);
|
|
}
|
|
}
|
|
|
|
/// Return `true` if the given [element] is a top-level member of the test
|
|
/// package.
|
|
bool _isInsideTestPackage(engine.FunctionElement element) {
|
|
var parent = element.enclosingElement;
|
|
return parent is engine.CompilationUnitElement &&
|
|
parent.source.fullName.endsWith('test.dart');
|
|
}
|
|
}
|