| // 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; |
| } |