| // 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/collections.dart'; | |
| import 'package:analysis_server/src/utilities/flutter.dart' as flutter; | |
| 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/source/line_info.dart'; | |
| import 'package:analyzer/src/generated/source.dart'; | |
| import 'package:analyzer_plugin/protocol/protocol_common.dart'; | |
| /** | |
| * A computer for [CompilationUnit] outline. | |
| */ | |
| class DartUnitOutlineComputer { | |
| final String file; | |
| final CompilationUnit unit; | |
| final LineInfo lineInfo; | |
| final bool withBasicFlutter; | |
| DartUnitOutlineComputer(this.file, this.lineInfo, this.unit, | |
| {this.withBasicFlutter: false}); | |
| /** | |
| * 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) { | |
| TypeAnnotation 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) { | |
| TypeAnnotation 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> _addFunctionBodyOutlines(FunctionBody body) { | |
| List<Outline> contents = <Outline>[]; | |
| body.accept(new _FunctionBodyOutlinesVisitor(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) { | |
| CharacterLocation lineLocation = lineInfo.getLocation(offset); | |
| int startLine = lineLocation.lineNumber; | |
| int startColumn = lineLocation.columnNumber; | |
| return new Location(file, offset, length, startLine, startColumn); | |
| } | |
| Outline _newClassOutline(ClassDeclaration node, List<Outline> classContents) { | |
| node.firstTokenAfterCommentAndMetadata; | |
| SimpleIdentifier nameNode = node.name; | |
| String name = nameNode.name; | |
| 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 _nodeOutline(node, element, classContents); | |
| } | |
| Outline _newClassTypeAlias(ClassTypeAlias node) { | |
| SimpleIdentifier nameNode = node.name; | |
| String name = nameNode.name; | |
| 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 _nodeOutline(node, element); | |
| } | |
| 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; | |
| } | |
| 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 = _addFunctionBodyOutlines(constructor.body); | |
| return _nodeOutline(constructor, element, contents); | |
| } | |
| Outline _newEnumConstant(EnumConstantDeclaration node) { | |
| SimpleIdentifier nameNode = node.name; | |
| String name = nameNode.name; | |
| Element element = new 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) { | |
| SimpleIdentifier nameNode = node.name; | |
| String name = nameNode.name; | |
| Element element = new Element( | |
| ElementKind.ENUM, | |
| name, | |
| Element.makeFlags( | |
| isPrivate: Identifier.isPrivateName(name), | |
| isDeprecated: _isDeprecated(node)), | |
| location: _getLocationNode(nameNode)); | |
| return _nodeOutline(node, element, children); | |
| } | |
| Outline _newFunctionOutline(FunctionDeclaration function, bool isStatic) { | |
| TypeAnnotation 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; | |
| } | |
| 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, | |
| typeParameters: | |
| _getTypeParametersStr(functionExpression.typeParameters)); | |
| List<Outline> contents = _addFunctionBodyOutlines(functionExpression.body); | |
| return _nodeOutline(function, element, contents); | |
| } | |
| Outline _newFunctionTypeAliasOutline(FunctionTypeAlias node) { | |
| TypeAnnotation returnType = node.returnType; | |
| SimpleIdentifier nameNode = node.name; | |
| String name = nameNode.name; | |
| 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 _nodeOutline(node, element); | |
| } | |
| Outline _newMethodOutline(MethodDeclaration method) { | |
| TypeAnnotation 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; | |
| } | |
| 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, | |
| typeParameters: _getTypeParametersStr(method.typeParameters)); | |
| List<Outline> contents = _addFunctionBodyOutlines(method.body); | |
| return _nodeOutline(method, element, contents); | |
| } | |
| Outline _newUnitOutline(List<Outline> unitContents) { | |
| Element element = new Element( | |
| ElementKind.COMPILATION_UNIT, '<unit>', Element.makeFlags(), | |
| location: _getLocationNode(unit)); | |
| return _nodeOutline(unit, element, unitContents); | |
| } | |
| Outline _newVariableOutline(String typeName, ElementKind kind, | |
| VariableDeclaration variable, bool isStatic) { | |
| SimpleIdentifier nameNode = variable.name; | |
| String name = nameNode.name; | |
| 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); | |
| return _nodeOutline(variable, element); | |
| } | |
| Outline _nodeOutline(AstNode node, Element element, | |
| [List<Outline> children]) { | |
| int offset = node.offset; | |
| int end = node.end; | |
| if (node is VariableDeclaration) { | |
| AstNode 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; | |
| } | |
| } | |
| } | |
| int codeOffset = node.offset; | |
| if (node is AnnotatedNode) { | |
| codeOffset = node.firstTokenAfterCommentAndMetadata.offset; | |
| } | |
| int length = end - offset; | |
| int codeLength = node.end - codeOffset; | |
| return new Outline(element, offset, length, codeOffset, codeLength, | |
| children: nullIfEmpty(children)); | |
| } | |
| 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.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 { | |
| final DartUnitOutlineComputer outlineComputer; | |
| final List<Outline> contents; | |
| _FunctionBodyOutlinesVisitor(this.outlineComputer, this.contents); | |
| /** | |
| * 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 | |
| visitFunctionDeclaration(FunctionDeclaration node) { | |
| contents.add(outlineComputer._newFunctionOutline(node, false)); | |
| } | |
| @override | |
| visitInstanceCreationExpression(InstanceCreationExpression node) { | |
| if (outlineComputer.withBasicFlutter && flutter.isWidgetCreation(node)) { | |
| List<Outline> children = <Outline>[]; | |
| node.argumentList | |
| .accept(new _FunctionBodyOutlinesVisitor(outlineComputer, children)); | |
| String text = flutter.getWidgetPresentationText(node); | |
| Element element = new Element(ElementKind.CONSTRUCTOR_INVOCATION, text, 0, | |
| location: outlineComputer._getLocationOffsetLength(node.offset, 0)); | |
| contents.add(new Outline( | |
| element, node.offset, node.length, node.offset, node.length, | |
| children: nullIfEmpty(children))); | |
| } else { | |
| super.visitInstanceCreationExpression(node); | |
| } | |
| } | |
| @override | |
| visitMethodInvocation(MethodInvocation node) { | |
| SimpleIdentifier nameNode = node.methodName; | |
| engine.Element nameElement = nameNode.staticElement; | |
| if (nameElement is! engine.ExecutableElement) { | |
| return; | |
| } | |
| engine.ExecutableElement executableElement = nameElement; | |
| String extractString(NodeList<Expression> arguments) { | |
| if (arguments != null && arguments.length > 0) { | |
| Expression argument = arguments[0]; | |
| if (argument is StringLiteral) { | |
| String value = argument.stringValue; | |
| if (value != null) { | |
| return value; | |
| } | |
| } | |
| return argument.toSource(); | |
| } | |
| return 'unnamed'; | |
| } | |
| void addOutlineNode(ElementKind kind, [List<Outline> children]) { | |
| String executableName = nameNode.name; | |
| String description = extractString(node.argumentList?.arguments); | |
| String name = '$executableName("$description")'; | |
| Element element = new Element(kind, name, 0, | |
| location: outlineComputer._getLocationNode(nameNode)); | |
| contents.add(new Outline( | |
| element, node.offset, node.length, node.offset, node.length, | |
| children: nullIfEmpty(children))); | |
| } | |
| if (isGroup(executableElement)) { | |
| List<Outline> groupContents = <Outline>[]; | |
| node.argumentList.accept( | |
| new _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) { | |
| engine.Element parent = element.enclosingElement; | |
| return parent is engine.CompilationUnitElement && | |
| parent.source.fullName.endsWith('test.dart'); | |
| } | |
| } |