| // Copyright (c) 2024, 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:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/element/element.dart'; | |
| import 'package:analyzer/dart/element/nullability_suffix.dart'; | |
| import 'package:analyzer/dart/element/type.dart'; | |
| import 'package:analyzer/src/dart/ast/ast.dart' show ToBeDeprecated; | |
| import 'package:analyzer/src/utilities/extensions/string.dart'; | |
| import 'package:collection/collection.dart'; | |
| const String widgetsUri = 'package:flutter/widgets.dart'; | |
| const _nameAlign = 'Align'; | |
| const _nameBuildContext = 'BuildContext'; | |
| const _nameBuilder = 'Builder'; | |
| const _nameCenter = 'Center'; | |
| const _nameContainer = 'Container'; | |
| const _nameExpanded = 'Expanded'; | |
| const _nameFlex = 'Flex'; | |
| const _nameFlexible = 'Flexible'; | |
| const _namePadding = 'Padding'; | |
| const _nameSizedBox = 'SizedBox'; | |
| const _nameState = 'State'; | |
| const _nameStatefulWidget = 'StatefulWidget'; | |
| const _nameStatelessWidget = 'StatelessWidget'; | |
| const _nameWidget = 'Widget'; | |
| final Uri _uriAlignment = Uri.parse( | |
| 'package:flutter/src/painting/alignment.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'); | |
| extension ArgumentExtension on Argument { | |
| /// Whether this is the `builder` argument. | |
| bool get isBuilderArgument { | |
| var self = this; | |
| return self is NamedArgument && self.name.lexeme == 'builder'; | |
| } | |
| /// Whether this is the `child` argument. | |
| bool get isChildArgument { | |
| var self = this; | |
| return self is NamedArgument && self.name.lexeme == 'child'; | |
| } | |
| /// Whether this is the `children` argument. | |
| bool get isChildrenArgument { | |
| var self = this; | |
| return self is NamedArgument && self.name.lexeme == 'children'; | |
| } | |
| /// Whether this is the `sliver` argument. | |
| bool get isSliverArgument { | |
| var self = this; | |
| return self is NamedArgument && self.name.lexeme == 'sliver'; | |
| } | |
| /// Whether this is the `slivers` argument. | |
| bool get isSliversArgument { | |
| var self = this; | |
| return self is NamedArgument && self.name.lexeme == 'slivers'; | |
| } | |
| } | |
| @ToBeDeprecated('Use AstNodeExtension2 instead.') | |
| extension AstNodeExtension on AstNode? { | |
| /// Returns the instance creation expression that surrounds this node, if any, | |
| /// and otherwise `null`. | |
| /// | |
| /// This node may be the instance creation expression itself or an (optionally | |
| /// prefixed) identifier that names the constructor. | |
| InstanceCreationExpression? get findInstanceCreationExpression { | |
| var node = this; | |
| if (node is ImportPrefixReference) { | |
| node = node.parent; | |
| } | |
| if (node is SimpleIdentifier) { | |
| node = node.parent; | |
| } | |
| if (node is PrefixedIdentifier) { | |
| node = node.parent; | |
| } | |
| if (node is NamedType) { | |
| node = node.parent; | |
| } | |
| if (node is ConstructorName) { | |
| node = node.parent; | |
| } | |
| if (node is InstanceCreationExpression) { | |
| return node; | |
| } | |
| return null; | |
| } | |
| /// Attempts to find and return the closest expression that encloses this | |
| /// and is an independent Flutter `Widget`. | |
| /// | |
| /// Returns `null` if nothing is found. | |
| Expression? get findWidgetExpression { | |
| for (var node = this; node != null; node = node.parent) { | |
| if (!node.isWidgetExpression) { | |
| if (node is ArgumentList || node is Statement || node is FunctionBody) { | |
| return null; | |
| } | |
| continue; | |
| } | |
| if (node is AssignmentExpression) { | |
| return null; | |
| } | |
| var parent = node.parent; | |
| 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 NamedArgument && parent.argumentExpression == node || | |
| parent is Statement || | |
| parent is SwitchExpressionCase && parent.expression == node || | |
| parent is VariableDeclaration) { | |
| return node as Expression; | |
| } | |
| } | |
| return null; | |
| } | |
| /// Whether this [AstNode] is the Flutter class `Widget`, or its subtype. | |
| bool get isWidgetExpression { | |
| return switch (this) { | |
| null => false, | |
| AstNode(parent: NamedType()) || | |
| AstNode(parent: AstNode(parent: NamedType())) => false, | |
| AstNode(parent: ConstructorName()) => false, | |
| NamedArgument() => false, | |
| Expression(:var staticType) => staticType.isWidgetType, | |
| _ => false, | |
| }; | |
| } | |
| /// Finds the named expression whose name is the given [name] that is an | |
| /// argument to a Flutter instance creation expression. | |
| /// | |
| /// Returns `null` if any condition cannot be satisfied. | |
| NamedArgument? findArgumentNamed(String name) { | |
| var self = this; | |
| NamedArgument? argument; | |
| if (self is NamedArgument) { | |
| argument = self; | |
| } else if (self?.parent case NamedArgument parent) { | |
| argument = parent; | |
| } else if (self is SimpleIdentifier) { | |
| var parent = self.parent; | |
| if (parent is Label && parent.parent is NamedArgument) { | |
| argument = parent.parent as NamedArgument; | |
| } | |
| } | |
| if (argument == null || argument.name.lexeme != name) { | |
| return null; | |
| } | |
| var invocation = argument.parent?.parent; | |
| if (invocation is! InstanceCreationExpression || | |
| !invocation.isWidgetCreation) { | |
| return null; | |
| } | |
| return argument; | |
| } | |
| } | |
| extension AstNodeExtension2 on AstNode? { | |
| /// Returns the constructor invocation that surrounds this node, if any, and | |
| /// otherwise `null`. | |
| /// | |
| /// This node may be the constructor invocation itself or a node in its | |
| /// constructor reference. | |
| ConstructorInvocation? get findConstructorInvocation { | |
| var node = this; | |
| if (node is ImportPrefixReference) { | |
| node = node.parent2; | |
| } | |
| if (node is ConstructorSelector) { | |
| node = node.parent2; | |
| } | |
| if (node is ConstructorTypeReference) { | |
| node = node.parent2; | |
| } | |
| if (node is ConstructorReference2) { | |
| node = node.parent2; | |
| } | |
| if (node is ConstructorInvocation) { | |
| return node; | |
| } | |
| return null; | |
| } | |
| /// Attempts to find and return the closest expression that encloses this | |
| /// and is an independent Flutter `Widget`. | |
| /// | |
| /// Returns `null` if nothing is found. | |
| Expression? get findWidgetExpression2 { | |
| for (var node = this; node != null; node = node.parent2) { | |
| if (!node.isWidgetExpression2) { | |
| if (node is ArgumentList || node is Statement || node is FunctionBody) { | |
| return null; | |
| } | |
| continue; | |
| } | |
| if (node is AssignmentExpression2) { | |
| return null; | |
| } | |
| var parent = node.parent2; | |
| if (parent is ArgumentList || | |
| parent is AssignmentExpression2 && parent.value == node || | |
| parent is ConditionalExpression && parent.thenExpression2 == node || | |
| parent is ConditionalExpression && parent.elseExpression2 == node || | |
| parent is ExpressionFunctionBody && parent.expression2 == node || | |
| parent is ForElement && parent.body2 == node || | |
| parent is IfElement && parent.thenElement2 == node || | |
| parent is IfElement && parent.elseElement2 == node || | |
| parent is ListLiteral || | |
| parent is NamedArgument && parent.argumentExpression2 == node || | |
| parent is Statement || | |
| parent is SwitchExpressionCase && parent.expression2 == node || | |
| parent is VariableDeclaration) { | |
| return node as Expression; | |
| } | |
| } | |
| return null; | |
| } | |
| /// Whether this [AstNode] is the Flutter class `Widget`, or its subtype. | |
| bool get isWidgetExpression2 { | |
| return switch (this) { | |
| null => false, | |
| AstNode(parent2: ConstructorTypeReference()) || | |
| AstNode(parent2: AstNode(parent2: ConstructorTypeReference())) => false, | |
| AstNode(parent2: ConstructorReference2()) => false, | |
| NamedArgument() => false, | |
| Expression(:var staticType) => staticType.isWidgetType, | |
| _ => false, | |
| }; | |
| } | |
| /// Finds the named expression whose name is the given [name] that is an | |
| /// argument to a Flutter instance creation expression. | |
| /// | |
| /// Returns `null` if any condition cannot be satisfied. | |
| NamedArgument? findArgumentNamed2(String name) { | |
| var self = this; | |
| NamedArgument? argument; | |
| if (self is NamedArgument) { | |
| argument = self; | |
| } else if (self?.parent2 case NamedArgument parent) { | |
| argument = parent; | |
| } | |
| if (argument == null || argument.name.lexeme != name) { | |
| return null; | |
| } | |
| var invocation = argument.parent2?.parent2; | |
| if (invocation is! ConstructorInvocation || !invocation.isWidgetCreation) { | |
| return null; | |
| } | |
| return argument; | |
| } | |
| } | |
| extension ClassElementExtension2 on ClassElement { | |
| /// Whether this is the Flutter class `State`. | |
| bool get isExactState => _isExactly(_nameState, _uriFramework); | |
| /// Whether this has the Flutter class `State` as a superclass. | |
| bool get isState => _hasSupertype(_uriFramework, _nameState); | |
| /// Whether this is a [ClassElement] that extends the Flutter class | |
| /// `StatefulWidget`. | |
| bool get isStatefulWidgetDeclaration => supertype.isExactlyStatefulWidgetType; | |
| } | |
| extension ConstructorInvocationExtension on ConstructorInvocation { | |
| /// The named expression representing the `builder` argument, or `null` if | |
| /// there is none. | |
| NamedArgument? get builderArgument => argumentList.arguments2 | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isBuilderArgument); | |
| /// The named expression representing the `child` argument, or `null` if there | |
| /// is none. | |
| NamedArgument? get childArgument => argumentList.arguments2 | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isChildArgument); | |
| /// The named expression representing the `children` argument, or `null` if | |
| /// there is none. | |
| NamedArgument? get childrenArgument => argumentList.arguments2 | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isChildrenArgument); | |
| bool get isExactlyAlignCreation => staticType.isExactWidgetTypeAlign; | |
| bool get isExactlyContainerCreation => staticType.isExactWidgetTypeContainer; | |
| bool get isExactlyPaddingCreation => staticType.isExactWidgetTypePadding; | |
| bool get isWidgetCreation { | |
| var element = constructorReference.element?.enclosingElement; | |
| return element.isWidget; | |
| } | |
| /// The named expression representing the `sliver` argument, or `null` if there | |
| /// is none. | |
| NamedArgument? get sliverArgument => argumentList.arguments2 | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isSliverArgument); | |
| /// The named expression representing the `slivers` argument, or `null` if | |
| /// there is none. | |
| NamedArgument? get sliversArgument => argumentList.arguments2 | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isSliversArgument); | |
| /// The presentation for this node. | |
| String? get widgetPresentationText { | |
| var element = constructorReference.element?.enclosingElement; | |
| if (!element.isWidget) { | |
| return null; | |
| } | |
| var arguments = argumentList.arguments2; | |
| if (element._isExactly('Icon', _uriWidgetsIcon)) { | |
| if (arguments.isNotEmpty) { | |
| var text = arguments[0].toString(); | |
| var arg = text.elideTo(32); | |
| return 'Icon($arg)'; | |
| } else { | |
| return 'Icon'; | |
| } | |
| } | |
| if (element._isExactly('Text', _uriWidgetsText)) { | |
| if (arguments.isNotEmpty) { | |
| var text = arguments[0].toString(); | |
| var arg = text.elideTo(32); | |
| return 'Text($arg)'; | |
| } else { | |
| return 'Text'; | |
| } | |
| } | |
| return element?.name; | |
| } | |
| } | |
| extension DartTypeExtension on DartType? { | |
| /// Whether this is the Flutter type `BuildContext`. | |
| bool get isBuildContext { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.nullabilitySuffix == NullabilitySuffix.none && | |
| self.element._isExactly(_nameBuildContext, _uriFramework); | |
| } | |
| /// Whether this is the 'dart.ui' class `Color`, or a subtype. | |
| bool get isColor { | |
| var self = this; | |
| if (self is! InterfaceType) { | |
| return false; | |
| } | |
| return [self, ...self.element.allSupertypes].any( | |
| (t) => t.element.name == 'Color' && t.element.library.name == 'dart.ui', | |
| ); | |
| } | |
| /// Whether this is the Flutter mixin `Diagnosticable` or a subtype. | |
| bool get isDiagnosticable { | |
| var self = this; | |
| if (self is! InterfaceType) { | |
| return false; | |
| } | |
| return [self, ...self.element.allSupertypes].any( | |
| (t) => | |
| t.element.name == 'Diagnosticable' && | |
| t.element.library.uri == _uriDiagnostics, | |
| ); | |
| } | |
| /// Whether this is the Flutter type `EdgeInsetsGeometry`. | |
| bool get isExactEdgeInsetsGeometryType { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly('EdgeInsetsGeometry', _uriEdgeInsets); | |
| } | |
| /// Whether this is the Flutter class `StatefulWidget`. | |
| bool get isExactlyStatefulWidgetType { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_nameStatefulWidget, _uriFramework); | |
| } | |
| /// Whether this is the Flutter class `StatelessWidget`. | |
| bool get isExactlyStatelessWidgetType { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_nameStatelessWidget, _uriFramework); | |
| } | |
| /// Whether this is the Flutter class `Align`. | |
| bool get isExactWidgetTypeAlign { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_nameAlign, _uriBasic); | |
| } | |
| /// Whether this is the Flutter class `Builder`. | |
| bool get isExactWidgetTypeBuilder { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_nameBuilder, _uriBasic); | |
| } | |
| /// Whether this is the Flutter class `Center`. | |
| bool get isExactWidgetTypeCenter { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_nameCenter, _uriBasic); | |
| } | |
| /// Whether this is the Flutter class `Container`. | |
| bool get isExactWidgetTypeContainer { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_nameContainer, _uriContainer); | |
| } | |
| /// Whether this is the Flutter class `Expanded`. | |
| bool get isExactWidgetTypeExpanded { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_nameExpanded, _uriBasic); | |
| } | |
| /// Whether this is the Flutter class `Flexible`. | |
| bool get isExactWidgetTypeFlexible { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_nameFlexible, _uriBasic); | |
| } | |
| /// Whether this is the Flutter class `Padding`. | |
| bool get isExactWidgetTypePadding { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_namePadding, _uriBasic); | |
| } | |
| /// Whether this is the Flutter class `SizedBox`. | |
| bool get isExactWidgetTypeSizedBox { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.element._isExactly(_nameSizedBox, _uriBasic); | |
| } | |
| /// Whether this is the Flutter class `Widget`, or its subtype. | |
| bool get isListOfWidgetsType { | |
| var self = this; | |
| return self is InterfaceType && | |
| self.isDartCoreList && | |
| self.typeArguments[0].isWidgetType; | |
| } | |
| /// Whether this is the vector_math_64 class `Matrix4`, or its | |
| /// subtype. | |
| bool get isMatrix4 { | |
| var self = this; | |
| if (self is! InterfaceType) { | |
| return false; | |
| } | |
| return [self, ...self.element.allSupertypes].any( | |
| (t) => | |
| t.element.name == 'Matrix4' && | |
| t.element.library.name == 'vector_math_64', | |
| ); | |
| } | |
| /// Whether this is a function type matching the Flutter typedef | |
| /// `WidgetBuilder` (i.e., `Widget Function(BuildContext context)`). | |
| bool get isWidgetBuilder { | |
| var self = this; | |
| return self is FunctionType && | |
| self.returnType.isWidgetType && | |
| self.formalParameters.length == 1 && | |
| self.formalParameters[0].type.isBuildContext; | |
| } | |
| /// Whether this is the Flutter class `Widget`, or its subtype. | |
| bool get isWidgetType { | |
| var self = this; | |
| return self is InterfaceType && self.element.isWidget; | |
| } | |
| } | |
| extension ElementAnnotationExtension on ElementAnnotation { | |
| static final Uri _flutterWidgetPreviewLibraryUri = Uri.parse( | |
| 'package:flutter/src/widget_previews/widget_previews.dart', | |
| ); | |
| /// Whether the annotation marks the associated member as being a widget | |
| /// preview. | |
| bool get isWidgetPreview { | |
| var element = this.element; | |
| if (element is! ConstructorElement) { | |
| return false; | |
| } | |
| return element.enclosingElement.name == 'Preview' && | |
| element.library.uri == _flutterWidgetPreviewLibraryUri; | |
| } | |
| } | |
| @ToBeDeprecated('Use ConstructorInvocationExtension instead.') | |
| extension InstanceCreationExpressionExtension on InstanceCreationExpression { | |
| /// The named expression representing the `builder` argument, or `null` if | |
| /// there is none. | |
| NamedArgument? get builderArgument => argumentList.arguments | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isBuilderArgument); | |
| /// The named expression representing the `child` argument, or `null` if there | |
| /// is none. | |
| NamedArgument? get childArgument => argumentList.arguments | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isChildArgument); | |
| /// The named expression representing the `children` argument, or `null` if | |
| /// there is none. | |
| NamedArgument? get childrenArgument => argumentList.arguments | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isChildrenArgument); | |
| bool get isExactlyAlignCreation => staticType.isExactWidgetTypeAlign; | |
| bool get isExactlyContainerCreation => staticType.isExactWidgetTypeContainer; | |
| bool get isExactlyPaddingCreation => staticType.isExactWidgetTypePadding; | |
| /// Whether this is a constructor invocation for a class that has the Flutter | |
| /// class `Widget` as a superclass. | |
| bool get isWidgetCreation { | |
| var element = constructorName.element?.enclosingElement; | |
| return element.isWidget; | |
| } | |
| /// The named expression representing the `sliver` argument, or `null` if there | |
| /// is none. | |
| NamedArgument? get sliverArgument => argumentList.arguments | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isSliverArgument); | |
| /// The named expression representing the `slivers` argument, or `null` if | |
| /// there is none. | |
| NamedArgument? get sliversArgument => argumentList.arguments | |
| .whereType<NamedArgument>() | |
| .firstWhereOrNull((argument) => argument.isSliversArgument); | |
| /// The presentation for this node. | |
| String? get widgetPresentationText { | |
| var element = constructorName.element?.enclosingElement; | |
| if (!element.isWidget) { | |
| return null; | |
| } | |
| var arguments = argumentList.arguments; | |
| if (element._isExactly('Icon', _uriWidgetsIcon)) { | |
| if (arguments.isNotEmpty) { | |
| var text = arguments[0].toString(); | |
| var arg = text.elideTo(32); | |
| return 'Icon($arg)'; | |
| } else { | |
| return 'Icon'; | |
| } | |
| } | |
| if (element._isExactly('Text', _uriWidgetsText)) { | |
| if (arguments.isNotEmpty) { | |
| var text = arguments[0].toString(); | |
| var arg = text.elideTo(32); | |
| return 'Text($arg)'; | |
| } else { | |
| return 'Text'; | |
| } | |
| } | |
| return element?.name; | |
| } | |
| } | |
| extension InterfaceElement2Extension on InterfaceElement? { | |
| /// Whether this is the Flutter class `Flex`, or a subtype. | |
| bool get isFlexWidget { | |
| var self = this; | |
| if (self is! ClassElement) { | |
| return false; | |
| } | |
| if (!self.isWidget) { | |
| return false; | |
| } | |
| if (_isExactly(_nameFlex, _uriBasic)) { | |
| return true; | |
| } | |
| return self.allSupertypes.any( | |
| (type) => type.element._isExactly(_nameFlex, _uriBasic), | |
| ); | |
| } | |
| } | |
| extension InterfaceElementExtension2 on InterfaceElement? { | |
| /// Whether this is the Flutter class `Alignment`. | |
| bool get isExactAlignment { | |
| return _isExactly('Alignment', _uriAlignment); | |
| } | |
| /// Whether this is the Flutter class `AlignmentDirectional`. | |
| bool get isExactAlignmentDirectional { | |
| return _isExactly('AlignmentDirectional', _uriAlignment); | |
| } | |
| /// Whether this is the Flutter class `AlignmentGeometry`. | |
| bool get isExactAlignmentGeometry { | |
| return _isExactly('AlignmentGeometry', _uriAlignment); | |
| } | |
| /// Whether this is the Flutter class `Widget`, or a subtype. | |
| bool get isWidget { | |
| var self = this; | |
| if (self is! ClassElement) { | |
| return false; | |
| } | |
| if (_isExactly(_nameWidget, _uriFramework)) { | |
| return true; | |
| } | |
| return self.allSupertypes.any( | |
| (type) => type.element._isExactly(_nameWidget, _uriFramework), | |
| ); | |
| } | |
| /// Whether this has a supertype with the [requiredName] defined in the file | |
| /// with the [requiredUri]. | |
| bool _hasSupertype(Uri requiredUri, String requiredName) { | |
| var self = this; | |
| if (self == null) { | |
| return false; | |
| } | |
| for (var type in self.allSupertypes) { | |
| if (type.element.name == requiredName) { | |
| var uri = type.element.library.uri; | |
| if (uri == requiredUri) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| /// Whether this is the exact [type] defined in the file with the given [uri]. | |
| bool _isExactly(String type, Uri uri) { | |
| var self = this; | |
| return self is ClassElement && self.name == type && self.library.uri == uri; | |
| } | |
| } | |
| extension NamedTypeExtension on NamedType { | |
| /// Whether this type is a valid return type for a function annotated with | |
| /// `@Preview(...)`. | |
| /// | |
| /// Valid widget preview return types are: | |
| /// - `Widget` | |
| /// - `Widget Function(BuildContext)` (aka `WidgetBuilder`) | |
| bool get isValidWidgetPreviewReturnType { | |
| var self = this; | |
| return self.type.isWidgetType || self.type.isWidgetBuilder; | |
| } | |
| } |