blob: b2540c65490cb555bf5dad1c8394f54278736ee1 [file] [log] [blame]
// 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: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';
/// Computer for Flutter specific outlines.
class FlutterOutlineComputer {
final ResolvedUnitResult resolvedUnit;
final List<protocol.FlutterOutline> _depthFirstOrder = [];
FlutterOutlineComputer(this.resolvedUnit);
Flutter get _flutter => Flutter.instance;
protocol.FlutterOutline compute() {
var dartOutline = DartUnitOutlineComputer(
resolvedUnit,
withBasicFlutter: false,
).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) {
parent.children ??= <protocol.FlutterOutline>[];
parent.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,
Expression argument, ParameterElement parameter) {
if (parameter == null) {
return;
}
protocol.Location nameLocation;
if (argument is NamedExpression) {
NamedExpression namedExpression = argument;
nameLocation = protocol.newLocation_fromNode(namedExpression.name.label);
argument = namedExpression.expression;
}
var valueLocation = protocol.newLocation_fromNode(argument);
var name = parameter.displayName;
var label = resolvedUnit.content.substring(argument.offset, argument.end);
if (label.contains('\n')) {
label = '…';
}
bool literalValueBoolean;
int literalValueInteger;
String literalValueString;
if (argument is BooleanLiteral) {
literalValueBoolean = argument.value;
} else if (argument is IntegerLiteral) {
literalValueInteger = argument.value;
} else if (argument is StringLiteral) {
literalValueString = argument.stringValue;
} else {
if (argument is FunctionExpression) {
var hasParameters = argument.parameters != null &&
argument.parameters.parameters.isNotEmpty;
if (argument.body is ExpressionFunctionBody) {
label = hasParameters ? '(…) => …' : '() => …';
} else {
label = hasParameters ? '(…) { … }' : '() { … }';
}
} else if (argument is ListLiteral) {
label = '[…]';
} else if (argument 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);
if (dartOutline.children != null) {
flutterOutline.children = dartOutline.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 (!_flutter.isWidgetType(type)) {
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) {
var isWidgetArgument = _flutter.isWidgetType(argument.staticType);
var isWidgetListArgument =
_flutter.isListOfWidgetsType(argument.staticType);
String parentAssociationLabel;
Expression childrenExpression;
if (argument is NamedExpression) {
parentAssociationLabel = argument.name.label.name;
childrenExpression = argument.expression;
} else {
childrenExpression = argument;
}
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) {
void addChildrenFrom(CollectionElement element) {
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.
}
}
addChildrenFrom(element);
}
}
} else {
var visitor = _FlutterOutlineBuilder(this);
argument.accept(visitor);
if (visitor.outlines.isNotEmpty) {
children.addAll(visitor.outlines);
} else {
var parameter = argument.staticParameterElement;
_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();
if (node.target != null) {
buffer.write(_getShortLabel(node.target));
buffer.write('.');
}
buffer.write(node.methodName.name);
if (node.argumentList == null || 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 = [];
_FlutterOutlineBuilder(this.computer);
@override
void visitExpression(Expression node) {
var outline = computer._createOutline(node, false);
if (outline != null) {
outlines.add(outline);
} else {
super.visitExpression(node);
}
}
}