| // Copyright (c) 2018, 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/computer/computer_outline.dart'; | |
| import 'package:analysis_server/src/protocol_server.dart' as protocol; | |
| 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'; | |
| import 'package:analyzer/dart/element/type.dart'; | |
| import 'package:analyzer/source/source_range.dart'; | |
| import 'package:analyzer/src/utilities/extensions/flutter.dart'; | |
| /// Computer for Flutter specific outlines. | |
| class FlutterOutlineComputer { | |
| final ResolvedUnitResult resolvedUnit; | |
| final List<protocol.FlutterOutline> _depthFirstOrder = []; | |
| new(this.resolvedUnit); | |
| protocol.FlutterOutline compute() { | |
| var dartOutline = DartUnitOutlineComputer(resolvedUnit).compute(); | |
| // Convert Dart outlines into Flutter outlines. | |
| var flutterDartOutline = _convert(dartOutline); | |
| // Create outlines for widgets. | |
| var visitor = _FlutterOutlineBuilder(this); | |
| resolvedUnit.unit.accept(visitor); | |
| // Associate Flutter outlines with Dart outlines. | |
| for (var outline in visitor.outlines) { | |
| for (var parent in _depthFirstOrder) { | |
| if (parent.offset < outline.offset && | |
| outline.offset + outline.length < parent.offset + parent.length) { | |
| var children = parent.children ??= <protocol.FlutterOutline>[]; | |
| children.add(outline); | |
| break; | |
| } | |
| } | |
| } | |
| return flutterDartOutline; | |
| } | |
| /// If the given [argument] for the [parameter] can be represented as a | |
| /// Flutter attribute, add it to the [attributes]. | |
| void _addAttribute( | |
| List<protocol.FlutterOutlineAttribute> attributes, | |
| Argument argument, | |
| FormalParameterElement? parameter, | |
| ) { | |
| if (parameter == null) { | |
| return; | |
| } | |
| protocol.Location? nameLocation; | |
| if (argument is NamedArgument) { | |
| nameLocation = protocol.newLocation_fromUnit( | |
| resolvedUnit.unit, | |
| SourceRange(argument.name.offset, argument.name.length), | |
| ); | |
| } | |
| var valueArgument = argument.argumentExpression; | |
| var valueLocation = protocol.newLocation_fromNode(valueArgument); | |
| var name = parameter.displayName; | |
| var label = resolvedUnit.content.substring( | |
| valueArgument.offset, | |
| valueArgument.end, | |
| ); | |
| if (label.contains('\n')) { | |
| label = '…'; | |
| } | |
| bool? literalValueBoolean; | |
| int? literalValueInteger; | |
| String? literalValueString; | |
| if (valueArgument is BooleanLiteral) { | |
| literalValueBoolean = valueArgument.value; | |
| } else if (valueArgument is IntegerLiteral) { | |
| literalValueInteger = valueArgument.value; | |
| } else if (valueArgument is StringLiteral) { | |
| literalValueString = valueArgument.stringValue; | |
| } else { | |
| if (valueArgument is FunctionExpression) { | |
| var parameters = valueArgument.parameters; | |
| var hasParameters = | |
| parameters != null && parameters.parameters.isNotEmpty; | |
| if (valueArgument.body is ExpressionFunctionBody) { | |
| label = hasParameters ? '(…) => …' : '() => …'; | |
| } else { | |
| label = hasParameters ? '(…) { … }' : '() { … }'; | |
| } | |
| } else if (valueArgument is ListLiteral) { | |
| label = '[…]'; | |
| } else if (valueArgument is SetOrMapLiteral) { | |
| label = '{…}'; | |
| } | |
| } | |
| attributes.add( | |
| protocol.FlutterOutlineAttribute( | |
| name, | |
| label, | |
| literalValueBoolean: literalValueBoolean, | |
| literalValueInteger: literalValueInteger, | |
| literalValueString: literalValueString, | |
| nameLocation: nameLocation, | |
| valueLocation: valueLocation, | |
| ), | |
| ); | |
| } | |
| protocol.FlutterOutline _convert(protocol.Outline dartOutline) { | |
| var flutterOutline = protocol.FlutterOutline( | |
| protocol.FlutterOutlineKind.DART_ELEMENT, | |
| dartOutline.offset, | |
| dartOutline.length, | |
| dartOutline.codeOffset, | |
| dartOutline.codeLength, | |
| dartElement: dartOutline.element, | |
| ); | |
| var children = dartOutline.children; | |
| if (children != null) { | |
| flutterOutline.children = children.map(_convert).toList(); | |
| } | |
| _depthFirstOrder.add(flutterOutline); | |
| return flutterOutline; | |
| } | |
| /// If the [node] is a supported Flutter widget creation, create a new | |
| /// outline item for it. If the node is not a widget creation, but its type | |
| /// is a Flutter Widget class subtype, and [withGeneric] is `true`, return | |
| /// a widget reference outline item. | |
| protocol.FlutterOutline? _createOutline(Expression node, bool withGeneric) { | |
| var type = node.staticType; | |
| if (type is! InterfaceType || !type.isWidgetType) { | |
| return null; | |
| } | |
| var className = type.element.displayName; | |
| if (node is InstanceCreationExpression) { | |
| var attributes = <protocol.FlutterOutlineAttribute>[]; | |
| var children = <protocol.FlutterOutline>[]; | |
| for (var argument in node.argumentList.arguments) { | |
| String? parentAssociationLabel; | |
| Expression childrenExpression; | |
| if (argument is NamedArgument) { | |
| parentAssociationLabel = argument.name.lexeme; | |
| } | |
| childrenExpression = argument.argumentExpression; | |
| void addChildrenFrom(CollectionElement? element) { | |
| if (element is ConditionalExpression) { | |
| addChildrenFrom(element.thenExpression); | |
| addChildrenFrom(element.elseExpression); | |
| } else if (element is Expression) { | |
| var child = _createOutline(element, true); | |
| if (child != null) { | |
| children.add(child); | |
| } | |
| } else if (element is IfElement) { | |
| addChildrenFrom(element.thenElement); | |
| addChildrenFrom(element.elseElement); | |
| } else if (element is ForElement) { | |
| addChildrenFrom(element.body); | |
| } else if (element is SpreadElement) { | |
| // Ignored. It's possible that we might be able to extract | |
| // some information from some spread expressions, but it seems | |
| // unlikely enough that we're not handling it at the moment. | |
| } else if (element is NullAwareElement) { | |
| addChildrenFrom(element.value); | |
| } | |
| } | |
| var argumentType = childrenExpression.staticType; | |
| var isWidgetArgument = argumentType.isWidgetType; | |
| var isWidgetListArgument = | |
| argumentType != null && argumentType.isListOfWidgetsType; | |
| if (isWidgetArgument && childrenExpression is ConditionalExpression) { | |
| addChildrenFrom(childrenExpression.thenExpression); | |
| addChildrenFrom(childrenExpression.elseExpression); | |
| } else if (isWidgetArgument) { | |
| var child = _createOutline(childrenExpression, true); | |
| if (child != null) { | |
| child.parentAssociationLabel = parentAssociationLabel; | |
| children.add(child); | |
| } | |
| } else if (isWidgetListArgument) { | |
| if (childrenExpression is ListLiteral) { | |
| for (var element in childrenExpression.elements) { | |
| addChildrenFrom(element); | |
| } | |
| } | |
| } else { | |
| var visitor = _FlutterOutlineBuilder(this); | |
| argument.accept(visitor); | |
| if (visitor.outlines.isNotEmpty) { | |
| children.addAll(visitor.outlines); | |
| } else { | |
| var parameter = argument.correspondingParameter; | |
| _addAttribute(attributes, argument, parameter); | |
| } | |
| } | |
| } | |
| return protocol.FlutterOutline( | |
| protocol.FlutterOutlineKind.NEW_INSTANCE, | |
| node.offset, | |
| node.length, | |
| node.offset, | |
| node.length, | |
| className: className, | |
| attributes: attributes, | |
| children: children, | |
| ); | |
| } | |
| // A generic Widget typed expression. | |
| if (withGeneric) { | |
| var kind = protocol.FlutterOutlineKind.GENERIC; | |
| String? variableName; | |
| if (node is SimpleIdentifier) { | |
| kind = protocol.FlutterOutlineKind.VARIABLE; | |
| variableName = node.name; | |
| } | |
| String? label; | |
| if (kind == protocol.FlutterOutlineKind.GENERIC) { | |
| label = _getShortLabel(node); | |
| } | |
| return protocol.FlutterOutline( | |
| kind, | |
| node.offset, | |
| node.length, | |
| node.offset, | |
| node.length, | |
| className: className, | |
| variableName: variableName, | |
| label: label, | |
| ); | |
| } | |
| return null; | |
| } | |
| String _getShortLabel(AstNode node) { | |
| if (node is MethodInvocation) { | |
| var buffer = StringBuffer(); | |
| var target = node.target; | |
| if (target != null) { | |
| buffer.write(_getShortLabel(target)); | |
| buffer.write('.'); | |
| } | |
| buffer.write(node.methodName.name); | |
| if (node.argumentList.arguments.isEmpty) { | |
| buffer.write('()'); | |
| } else { | |
| buffer.write('(…)'); | |
| } | |
| return buffer.toString(); | |
| } | |
| return node.toString(); | |
| } | |
| } | |
| class _FlutterOutlineBuilder extends GeneralizingAstVisitor<void> { | |
| final FlutterOutlineComputer computer; | |
| final List<protocol.FlutterOutline> outlines = []; | |
| new(this.computer); | |
| @override | |
| void visitExpression(Expression node) { | |
| var outline = computer._createOutline(node, false); | |
| if (outline != null) { | |
| outlines.add(outline); | |
| } else { | |
| super.visitExpression(node); | |
| } | |
| } | |
| } |