| // 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/utilities/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'; |
| |
| class Flutter { |
| static final Flutter instance = Flutter(); |
| |
| static const _nameAlign = 'Align'; |
| static const _nameBuilder = 'Builder'; |
| static const _nameCenter = 'Center'; |
| static const _nameContainer = 'Container'; |
| static const _namePadding = 'Padding'; |
| static const _nameSizedBox = 'SizedBox'; |
| static const _nameState = 'State'; |
| static const _nameStatefulWidget = 'StatefulWidget'; |
| static const _nameStatelessWidget = 'StatelessWidget'; |
| static const _nameStreamBuilder = 'StreamBuilder'; |
| static const _nameWidget = 'Widget'; |
| |
| final String widgetsUri = 'package:flutter/widgets.dart'; |
| |
| final Uri _uriAlignment = Uri.parse( |
| 'package:flutter/src/painting/alignment.dart', |
| ); |
| final Uri _uriAsync = Uri.parse( |
| 'package:flutter/src/widgets/async.dart', |
| ); |
| final Uri _uriBasic = Uri.parse( |
| 'package:flutter/src/widgets/basic.dart', |
| ); |
| final Uri _uriContainer = Uri.parse( |
| 'package:flutter/src/widgets/container.dart', |
| ); |
| final Uri _uriDiagnostics = Uri.parse( |
| 'package:flutter/src/foundation/diagnostics.dart', |
| ); |
| final Uri _uriEdgeInsets = Uri.parse( |
| 'package:flutter/src/painting/edge_insets.dart', |
| ); |
| final Uri _uriFramework = Uri.parse( |
| 'package:flutter/src/widgets/framework.dart', |
| ); |
| final Uri _uriWidgetsIcon = Uri.parse( |
| 'package:flutter/src/widgets/icon.dart', |
| ); |
| final Uri _uriWidgetsText = Uri.parse( |
| 'package:flutter/src/widgets/text.dart', |
| ); |
| |
| /// Return the argument with the given [index], or `null` if none. |
| Expression? argumentByIndex(List<Expression> arguments, int index) { |
| if (index < arguments.length) { |
| return arguments[index]; |
| } |
| return null; |
| } |
| |
| /// Return the named expression with the given [name], or `null` if none. |
| NamedExpression? argumentByName(List<Expression> arguments, String name) { |
| for (var argument in arguments) { |
| if (argument is NamedExpression && argument.name.label.name == name) { |
| return argument; |
| } |
| } |
| return null; |
| } |
| |
| void convertChildToChildren( |
| InstanceCreationExpression childArg, |
| NamedExpression namedExp, |
| String eol, |
| Function getNodeText, |
| Function getLinePrefix, |
| Function getIndent, |
| Function getText, |
| Function _addInsertEdit, |
| Function _addRemoveEdit, |
| Function _addReplaceEdit, |
| Function rangeNode) { |
| var childLoc = namedExp.offset + 'child'.length; |
| _addInsertEdit(childLoc, 'ren'); |
| var listLoc = childArg.offset; |
| String childArgSrc = getNodeText(childArg); |
| if (!childArgSrc.contains(eol)) { |
| _addInsertEdit(listLoc, '['); |
| _addInsertEdit(listLoc + childArg.length, ']'); |
| } else { |
| var newlineLoc = childArgSrc.lastIndexOf(eol); |
| if (newlineLoc == childArgSrc.length) { |
| newlineLoc -= 1; |
| } |
| String indentOld = |
| getLinePrefix(childArg.offset + eol.length + newlineLoc); |
| var indentNew = '$indentOld${getIndent(1)}'; |
| // The separator includes 'child:' but that has no newlines. |
| String separator = |
| getText(namedExp.offset, childArg.offset - namedExp.offset); |
| var prefix = separator.contains(eol) ? '' : '$eol$indentNew'; |
| if (prefix.isEmpty) { |
| _addInsertEdit(namedExp.offset + 'child:'.length, ' ['); |
| _addRemoveEdit(SourceRange(childArg.offset - 2, 2)); |
| } else { |
| _addInsertEdit(listLoc, '['); |
| } |
| var newChildArgSrc = childArgSrc.replaceAll( |
| 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) { |
| var childLoc = namedExp.offset + 'child'.length; |
| builder.addSimpleInsertion(childLoc, 'ren'); |
| var listLoc = childArg.offset; |
| String childArgSrc = getNodeText(childArg); |
| if (!childArgSrc.contains(eol)) { |
| builder.addSimpleInsertion(listLoc, '['); |
| builder.addSimpleInsertion(listLoc + childArg.length, ']'); |
| } else { |
| var newlineLoc = childArgSrc.lastIndexOf(eol); |
| if (newlineLoc == childArgSrc.length) { |
| newlineLoc -= 1; |
| } |
| String indentOld = |
| getLinePrefix(childArg.offset + eol.length + newlineLoc); |
| var indentNew = '$indentOld${getIndent(1)}'; |
| // The separator includes 'child:' but that has no newlines. |
| String separator = |
| getText(namedExp.offset, childArg.offset - namedExp.offset); |
| var prefix = separator.contains(eol) ? '' : '$eol$indentNew'; |
| if (prefix.isEmpty) { |
| builder.addSimpleInsertion(namedExp.offset + 'child:'.length, ' ['); |
| builder.addDeletion(SourceRange(childArg.offset - 2, 2)); |
| } else { |
| builder.addSimpleInsertion(listLoc, '['); |
| } |
| var newChildArgSrc = childArgSrc.replaceAll( |
| 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) { |
| for (var argument in newExpr.argumentList.arguments) { |
| if (argument is NamedExpression && isChildArgument(argument)) { |
| return argument; |
| } |
| } |
| return null; |
| } |
| |
| /// Return the named expression representing the `children` argument of the |
| /// given [newExpr], or `null` if none. |
| NamedExpression? findChildrenArgument(InstanceCreationExpression newExpr) { |
| for (var argument in newExpr.argumentList.arguments) { |
| if (isChildrenArgument(argument)) { |
| return argument as NamedExpression; |
| } |
| } |
| return 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) { |
| var child = findChildArgument(newExpr); |
| var widget = getChildWidget(child); |
| if (widget is InstanceCreationExpression) { |
| return widget; |
| } |
| return null; |
| } |
| |
| /// Return the named expression with the given [name], or `null` if none. |
| NamedExpression? findNamedArgument( |
| InstanceCreationExpression creation, |
| String name, |
| ) { |
| var arguments = creation.argumentList.arguments; |
| return argumentByName(arguments, name); |
| } |
| |
| /// 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; |
| } |
| var parent = node.parent; |
| var grandParent = parent?.parent; |
| if (parent is Label && grandParent is NamedExpression) { |
| if (node.name != name) { |
| return null; |
| } |
| } else { |
| return null; |
| } |
| var invocation = grandParent.parent?.parent; |
| if (invocation is! InstanceCreationExpression || |
| !isWidgetCreation(invocation)) { |
| return null; |
| } |
| return grandParent; |
| } |
| |
| /// Return the expression that is a Flutter Widget that is the value of the |
| /// given [child], or null if none. |
| Expression? getChildWidget(NamedExpression? child) { |
| var expression = child?.expression; |
| if (isWidgetExpression(expression)) { |
| return expression; |
| } |
| return null; |
| } |
| |
| /// Return the presentation for the given Flutter `Widget` creation [node]. |
| String? getWidgetPresentationText(InstanceCreationExpression node) { |
| var element = node.constructorName.staticElement?.enclosingElement; |
| if (!isWidget(element)) { |
| return null; |
| } |
| List<Expression> arguments = node.argumentList.arguments; |
| if (_isExactWidget(element, 'Icon', _uriWidgetsIcon)) { |
| if (arguments.isNotEmpty) { |
| var text = arguments[0].toString(); |
| var arg = shorten(text, 32); |
| return 'Icon($arg)'; |
| } else { |
| return 'Icon'; |
| } |
| } |
| if (_isExactWidget(element, 'Text', _uriWidgetsText)) { |
| if (arguments.isNotEmpty) { |
| var text = arguments[0].toString(); |
| var 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) { |
| var parent = node.parent; |
| var grandParent = parent?.parent; |
| var greatGrandParent = grandParent?.parent; |
| if (parent is ConstructorName && |
| grandParent is InstanceCreationExpression) { |
| newExpr = grandParent; |
| } else if (grandParent is ConstructorName && |
| greatGrandParent is InstanceCreationExpression) { |
| newExpr = greatGrandParent; |
| } |
| } 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 (node is AssignmentExpression) { |
| return null; |
| } |
| if (parent is AssignmentExpression) { |
| if (parent.rightHandSide == node) { |
| return node as Expression; |
| } |
| return null; |
| } |
| |
| if (parent is ArgumentList || |
| parent is ConditionalExpression && parent.thenExpression == node || |
| parent is ConditionalExpression && parent.elseExpression == node || |
| parent is ExpressionFunctionBody && parent.expression == node || |
| parent is ForElement && parent.body == node || |
| parent is IfElement && parent.thenElement == node || |
| parent is IfElement && parent.elseElement == node || |
| parent is ListLiteral || |
| parent is NamedExpression && parent.expression == node || |
| parent is Statement) { |
| return node as Expression; |
| } |
| } |
| 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 dart.ui class `Color`, or its |
| /// subtype. |
| bool isColor(DartType type) { |
| if (type is! InterfaceType) { |
| return false; |
| } |
| |
| bool isColorElement(ClassElement element) { |
| bool isExactColor(ClassElement element) => |
| element.name == 'Color' && element.library.name == 'dart.ui'; |
| |
| if (isExactColor(element)) { |
| return true; |
| } |
| for (var type in element.allSupertypes) { |
| if (isExactColor(type.element)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| return isColorElement(type.element); |
| } |
| |
| /// Return `true` if the given [type] is the flutter mixin `Diagnosticable` |
| /// or its subtype. |
| bool isDiagnosticable(DartType? type) { |
| if (type is! InterfaceType) { |
| return false; |
| } |
| |
| bool isDiagnosticableElement(ClassElement element) { |
| bool isExactDiagnosticable(ClassElement element) => |
| element.name == 'Diagnosticable' && |
| element.source.uri == _uriDiagnostics; |
| |
| if (isExactDiagnosticable(element)) { |
| return true; |
| } |
| for (var type in element.allSupertypes) { |
| if (isExactDiagnosticable(type.element)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| return isDiagnosticableElement(type.element); |
| } |
| |
| /// Return `true` if the [element] is the Flutter class `Alignment`. |
| bool isExactAlignment(ClassElement element) { |
| return _isExactWidget(element, 'Alignment', _uriAlignment); |
| } |
| |
| /// Return `true` if the [element] is the Flutter class |
| /// `AlignmentDirectional`. |
| bool isExactAlignmentDirectional(ClassElement element) { |
| return _isExactWidget(element, 'AlignmentDirectional', _uriAlignment); |
| } |
| |
| /// Return `true` if the [element] is the Flutter class `AlignmentGeometry`. |
| bool isExactAlignmentGeometry(ClassElement element) { |
| return _isExactWidget(element, 'AlignmentGeometry', _uriAlignment); |
| } |
| |
| /// Return `true` if the [type] is the Flutter type `EdgeInsetsGeometry`. |
| bool isExactEdgeInsetsGeometryType(DartType type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, 'EdgeInsetsGeometry', _uriEdgeInsets); |
| } |
| |
| /// Return `true` if the [node] is creation of `Align`. |
| bool isExactlyAlignCreation(InstanceCreationExpression node) { |
| var type = node.staticType; |
| return isExactWidgetTypeAlign(type); |
| } |
| |
| /// Return `true` if the [node] is creation of `Container`. |
| bool isExactlyContainerCreation(InstanceCreationExpression? node) { |
| var type = node?.staticType; |
| return isExactWidgetTypeContainer(type); |
| } |
| |
| /// Return `true` if the [node] is creation of `Padding`. |
| bool isExactlyPaddingCreation(InstanceCreationExpression node) { |
| var type = node.staticType; |
| return isExactWidgetTypePadding(type); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `StatefulWidget`. |
| bool isExactlyStatefulWidgetType(DartType? type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, _nameStatefulWidget, _uriFramework); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `StatelessWidget`. |
| bool isExactlyStatelessWidgetType(DartType type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, _nameStatelessWidget, _uriFramework); |
| } |
| |
| /// Return `true` if the given [element] is the Flutter class `State`. |
| bool isExactState(ClassElement element) { |
| return _isExactWidget(element, _nameState, _uriFramework); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `Align`. |
| bool isExactWidgetTypeAlign(DartType? type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, _nameAlign, _uriBasic); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `StreamBuilder`. |
| bool isExactWidgetTypeBuilder(DartType type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, _nameBuilder, _uriBasic); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `Center`. |
| bool isExactWidgetTypeCenter(DartType type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, _nameCenter, _uriBasic); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `Container`. |
| bool isExactWidgetTypeContainer(DartType? type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, _nameContainer, _uriContainer); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `Padding`. |
| bool isExactWidgetTypePadding(DartType? type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, _namePadding, _uriBasic); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `SizedBox`. |
| bool isExactWidgetTypeSizedBox(DartType type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, _nameSizedBox, _uriBasic); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `StreamBuilder`. |
| bool isExactWidgetTypeStreamBuilder(DartType type) { |
| return type is InterfaceType && |
| _isExactWidget(type.element, _nameStreamBuilder, _uriAsync); |
| } |
| |
| /// Return `true` if the given [type] is the Flutter class `Widget`, or its |
| /// subtype. |
| bool isListOfWidgetsType(DartType type) { |
| return type is InterfaceType && |
| type.isDartCoreList && |
| isWidgetType(type.typeArguments[0]); |
| } |
| |
| /// Return `true` if the given [type] is the vector_math_64 class `Matrix4`, |
| /// or its subtype. |
| bool isMatrix4(DartType type) { |
| if (type is! InterfaceType) { |
| return false; |
| } |
| |
| bool isMatrix4Element(ClassElement element) { |
| bool isExactMatrix4(ClassElement element) => |
| element.name == 'Matrix4' && element.library.name == 'vector_math_64'; |
| |
| if (isExactMatrix4(element)) { |
| return true; |
| } |
| for (var type in element.allSupertypes) { |
| if (isExactMatrix4(type.element)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| return isMatrix4Element(type.element); |
| } |
| |
| /// Return `true` if the given [element] has the Flutter class `State` as |
| /// a superclass. |
| bool isState(ClassElement? element) { |
| return _hasSupertype(element, _uriFramework, _nameState); |
| } |
| |
| /// 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, _nameWidget, _uriFramework)) { |
| return true; |
| } |
| for (var type in element.allSupertypes) { |
| if (_isExactWidget(type.element, _nameWidget, _uriFramework)) { |
| 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) { |
| var element = expr?.constructorName.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 NamedType || node.parent?.parent is NamedType) { |
| 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 (var type in element.allSupertypes) { |
| if (type.element.name == requiredName) { |
| var 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, Uri uri) { |
| return element != null && element.name == type && element.source.uri == uri; |
| } |
| } |