blob: f116530a504a67ac582959d56595686fd48018e7 [file] [log] [blame]
// Copyright (c) 2017, 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/services/correction/strings.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
const WIDGETS_LIBRARY_URI = 'package:flutter/widgets.dart';
const _BASIC_URI = "package:flutter/src/widgets/basic.dart";
const _CENTER_NAME = "Center";
const _CONTAINER_NAME = "Container";
const _CONTAINER_URI = "package:flutter/src/widgets/container.dart";
const _PADDING_NAME = "Padding";
const _STATE_NAME = "State";
const _STATEFUL_WIDGET_NAME = "StatefulWidget";
const _STATELESS_WIDGET_NAME = "StatelessWidget";
const _WIDGET_NAME = "Widget";
const _WIDGET_URI = "package:flutter/src/widgets/framework.dart";
final _frameworkUri = Uri.parse('package:flutter/src/widgets/framework.dart');
void convertChildToChildren(
InstanceCreationExpression childArg,
NamedExpression namedExp,
String eol,
Function getNodeText,
Function getLinePrefix,
Function getIndent,
Function getText,
Function _addInsertEdit,
Function _addRemoveEdit,
Function _addReplaceEdit,
Function rangeNode) {
int childLoc = namedExp.offset + 'child'.length;
_addInsertEdit(childLoc, 'ren');
int listLoc = childArg.offset;
String childArgSrc = getNodeText(childArg);
if (!childArgSrc.contains(eol)) {
_addInsertEdit(listLoc, '<Widget>[');
_addInsertEdit(listLoc + childArg.length, ']');
} else {
int newlineLoc = childArgSrc.lastIndexOf(eol);
if (newlineLoc == childArgSrc.length) {
newlineLoc -= 1;
}
String indentOld = getLinePrefix(childArg.offset + 1 + newlineLoc);
String indentNew = '$indentOld${getIndent(1)}';
// The separator includes 'child:' but that has no newlines.
String separator =
getText(namedExp.offset, childArg.offset - namedExp.offset);
String prefix = separator.contains(eol) ? "" : "$eol$indentNew";
if (prefix.isEmpty) {
_addInsertEdit(namedExp.offset + 'child:'.length, ' <Widget>[');
_addRemoveEdit(new SourceRange(childArg.offset - 2, 2));
} else {
_addInsertEdit(listLoc, '<Widget>[');
}
String newChildArgSrc = childArgSrc.replaceAll(
new RegExp("^$indentOld", multiLine: true), "$indentNew");
newChildArgSrc = "$prefix$newChildArgSrc,$eol$indentOld]";
_addReplaceEdit(rangeNode(childArg), newChildArgSrc);
}
}
void convertChildToChildren2(
DartFileEditBuilder builder,
Expression childArg,
NamedExpression namedExp,
String eol,
Function getNodeText,
Function getLinePrefix,
Function getIndent,
Function getText,
Function rangeNode) {
int childLoc = namedExp.offset + 'child'.length;
builder.addSimpleInsertion(childLoc, 'ren');
int listLoc = childArg.offset;
String childArgSrc = getNodeText(childArg);
if (!childArgSrc.contains(eol)) {
builder.addSimpleInsertion(listLoc, '<Widget>[');
builder.addSimpleInsertion(listLoc + childArg.length, ']');
} else {
int newlineLoc = childArgSrc.lastIndexOf(eol);
if (newlineLoc == childArgSrc.length) {
newlineLoc -= 1;
}
String indentOld = getLinePrefix(childArg.offset + 1 + newlineLoc);
String indentNew = '$indentOld${getIndent(1)}';
// The separator includes 'child:' but that has no newlines.
String separator =
getText(namedExp.offset, childArg.offset - namedExp.offset);
String prefix = separator.contains(eol) ? "" : "$eol$indentNew";
if (prefix.isEmpty) {
builder.addSimpleInsertion(
namedExp.offset + 'child:'.length, ' <Widget>[');
builder.addDeletion(new SourceRange(childArg.offset - 2, 2));
} else {
builder.addSimpleInsertion(listLoc, '<Widget>[');
}
String newChildArgSrc = childArgSrc.replaceAll(
new RegExp("^$indentOld", multiLine: true), "$indentNew");
newChildArgSrc = "$prefix$newChildArgSrc,$eol$indentOld]";
builder.addSimpleReplacement(rangeNode(childArg), newChildArgSrc);
}
}
/**
* Return the named expression representing the `child` argument of the given
* [newExpr], or `null` if none.
*/
NamedExpression findChildArgument(InstanceCreationExpression newExpr) =>
newExpr.argumentList.arguments
.firstWhere(isChildArgument, orElse: () => null);
/**
* Return the named expression representing the `children` argument of the
* given [newExpr], or `null` if none.
*/
NamedExpression findChildrenArgument(InstanceCreationExpression newExpr) =>
newExpr.argumentList.arguments
.firstWhere(isChildrenArgument, orElse: () => null);
/**
* Return the Flutter instance creation expression that is the value of the
* 'child' argument of the given [newExpr], or null if none.
*/
InstanceCreationExpression findChildWidget(InstanceCreationExpression newExpr) {
NamedExpression child = findChildArgument(newExpr);
return getChildWidget(child);
}
/**
* If the given [node] is a simple identifier, find the named expression whose
* name is the given [name] that is an argument to a Flutter instance creation
* expression. Return null if any condition cannot be satisfied.
*/
NamedExpression findNamedExpression(AstNode node, String name) {
if (node is! SimpleIdentifier) {
return null;
}
SimpleIdentifier namedArg = node;
NamedExpression namedExp;
if (namedArg.parent is Label && namedArg.parent.parent is NamedExpression) {
namedExp = namedArg.parent.parent;
if (namedArg.name != name || namedExp.expression == null) {
return null;
}
} else {
return null;
}
if (namedExp.parent?.parent is! InstanceCreationExpression) {
return null;
}
InstanceCreationExpression newExpr = namedExp.parent.parent;
if (newExpr == null || !isWidgetCreation(newExpr)) {
return null;
}
return namedExp;
}
/**
* Return the expression that is a Flutter Widget that is the value of the
* given [child], or null if none.
*/
Expression getChildWidget(NamedExpression child) {
Expression expression = child?.expression;
if (isWidgetExpression(expression)) {
return expression;
}
return null;
}
/**
* Return the presentation for the given Flutter `Widget` creation [node].
*/
String getWidgetPresentationText(InstanceCreationExpression node) {
ClassElement element = node.staticElement?.enclosingElement;
if (!isWidget(element)) {
return null;
}
List<Expression> arguments = node.argumentList.arguments;
if (_isExactWidget(
element, 'Icon', 'package:flutter/src/widgets/icon.dart')) {
if (arguments.isNotEmpty) {
String text = arguments[0].toString();
String arg = shorten(text, 32);
return 'Icon($arg)';
} else {
return 'Icon';
}
}
if (_isExactWidget(
element, 'Text', 'package:flutter/src/widgets/text.dart')) {
if (arguments.isNotEmpty) {
String text = arguments[0].toString();
String arg = shorten(text, 32);
return 'Text($arg)';
} else {
return 'Text';
}
}
return element.name;
}
/**
* Return the instance creation expression that surrounds the given
* [node], if any, else null. The [node] may be the instance creation
* expression itself or the identifier that names the constructor.
*/
InstanceCreationExpression identifyNewExpression(AstNode node) {
InstanceCreationExpression newExpr;
if (node is SimpleIdentifier) {
if (node.parent is ConstructorName &&
node.parent.parent is InstanceCreationExpression) {
newExpr = node.parent.parent;
} else if (node.parent?.parent is ConstructorName &&
node.parent.parent?.parent is InstanceCreationExpression) {
newExpr = node.parent.parent.parent;
}
} else if (node is InstanceCreationExpression) {
newExpr = node;
}
return newExpr;
}
/**
* Attempt to find and return the closest expression that encloses the [node]
* and is an independent Flutter `Widget`. Return `null` if nothing found.
*/
Expression identifyWidgetExpression(AstNode node) {
for (; node != null; node = node.parent) {
if (isWidgetExpression(node)) {
var parent = node.parent;
if (parent is ArgumentList ||
parent is ListLiteral ||
parent is NamedExpression && parent.expression == node ||
parent is Statement) {
return node;
}
}
if (node is ArgumentList || node is Statement || node is FunctionBody) {
return null;
}
}
return null;
}
/**
* Return `true` is the given [argument] is the `child` argument.
*/
bool isChildArgument(Expression argument) =>
argument is NamedExpression && argument.name.label.name == 'child';
/**
* Return `true` is the given [argument] is the `child` argument.
*/
bool isChildrenArgument(Expression argument) =>
argument is NamedExpression && argument.name.label.name == 'children';
/**
* Return `true` if the given [type] is the Flutter class `StatefulWidget`.
*/
bool isExactlyStatefulWidgetType(DartType type) {
return type is InterfaceType &&
_isExactWidget(type.element, _STATEFUL_WIDGET_NAME, _WIDGET_URI);
}
/**
* Return `true` if the given [type] is the Flutter class `StatelessWidget`.
*/
bool isExactlyStatelessWidgetType(DartType type) {
return type is InterfaceType &&
_isExactWidget(type.element, _STATELESS_WIDGET_NAME, _WIDGET_URI);
}
/// Return `true` if the given [element] is the Flutter class `State`.
bool isExactState(ClassElement element) {
return _isExactWidget(element, _STATE_NAME, _WIDGET_URI);
}
/**
* Return `true` if the given [type] is the Flutter class `Center`.
*/
bool isExactWidgetTypeCenter(DartType type) {
return type is InterfaceType &&
_isExactWidget(type.element, _CENTER_NAME, _BASIC_URI);
}
/**
* Return `true` if the given [type] is the Flutter class `Container`.
*/
bool isExactWidgetTypeContainer(DartType type) {
return type is InterfaceType &&
_isExactWidget(type.element, _CONTAINER_NAME, _CONTAINER_URI);
}
/**
* Return `true` if the given [type] is the Flutter class `Padding`.
*/
bool isExactWidgetTypePadding(DartType type) {
return type is InterfaceType &&
_isExactWidget(type.element, _PADDING_NAME, _BASIC_URI);
}
/**
* Return `true` if the given [type] is the Flutter class `Widget`, or its
* subtype.
*/
bool isListOfWidgetsType(DartType type) {
return type is InterfaceType &&
type.element.library.isDartCore &&
type.element.name == 'List' &&
type.typeArguments.length == 1 &&
isWidgetType(type.typeArguments[0]);
}
/// Return `true` if the given [element] has the Flutter class `State` as
/// a superclass.
bool isState(ClassElement element) {
return _hasSupertype(element, _frameworkUri, _STATE_NAME);
}
/**
* Return `true` if the given [element] is a [ClassElement] that extends
* the Flutter class `StatefulWidget`.
*/
bool isStatefulWidgetDeclaration(Element element) {
if (element is ClassElement) {
return isExactlyStatefulWidgetType(element.supertype);
}
return false;
}
/**
* Return `true` if the given [element] is the Flutter class `Widget`, or its
* subtype.
*/
bool isWidget(ClassElement element) {
if (element == null) {
return false;
}
if (_isExactWidget(element, _WIDGET_NAME, _WIDGET_URI)) {
return true;
}
for (InterfaceType type in element.allSupertypes) {
if (_isExactWidget(type.element, _WIDGET_NAME, _WIDGET_URI)) {
return true;
}
}
return false;
}
/**
* Return `true` if the given [expr] is a constructor invocation for a
* class that has the Flutter class `Widget` as a superclass.
*/
bool isWidgetCreation(InstanceCreationExpression expr) {
ClassElement element = expr?.staticElement?.enclosingElement;
return isWidget(element);
}
/**
* Return `true` if the given [node] is the Flutter class `Widget`, or its
* subtype.
*/
bool isWidgetExpression(AstNode node) {
if (node == null) {
return false;
}
if (node.parent is TypeName || node.parent?.parent is TypeName) {
return false;
}
if (node.parent is ConstructorName) {
return false;
}
if (node is NamedExpression) {
return false;
}
if (node is Expression) {
return isWidgetType(node.staticType);
}
return false;
}
/**
* Return `true` if the given [type] is the Flutter class `Widget`, or its
* subtype.
*/
bool isWidgetType(DartType type) {
return type is InterfaceType && isWidget(type.element);
}
/// Return `true` if the given [element] has a supertype with the [requiredName]
/// defined in the file with the [requiredUri].
bool _hasSupertype(ClassElement element, Uri requiredUri, String requiredName) {
if (element == null) {
return false;
}
for (InterfaceType type in element.allSupertypes) {
if (type.name == requiredName) {
Uri uri = type.element.source.uri;
if (uri == requiredUri) {
return true;
}
}
}
return false;
}
/**
* Return `true` if the given [element] is the exact [type] defined in the
* file with the given [uri].
*/
bool _isExactWidget(ClassElement element, String type, String uri) {
return element != null &&
element.name == type &&
element.source.uri.toString() == uri;
}