blob: 3495da2ad7034b7776b2c106dcd96a2bb0f16fc3 [file] [log] [blame]
// 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/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/utilities/extensions/string.dart';
import 'package:collection/collection.dart';
const String widgetsUri = 'package:flutter/widgets.dart';
const _nameAlign = 'Align';
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 _nameStreamBuilder = 'StreamBuilder';
const _nameWidget = 'Widget';
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',
);
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 NamedExpression && parent.expression == 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,
NamedExpression() => 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 this is not a [SimpleIdentifier], or if any other
/// condition cannot be satisfied.
NamedExpression? findArgumentNamed(String name) {
var self = this;
if (self is! SimpleIdentifier) {
return null;
}
var parent = self.parent;
var grandParent = parent?.parent;
if (parent is Label && grandParent is NamedExpression) {
if (self.name != name) {
return null;
}
} else {
return null;
}
var invocation = grandParent.parent?.parent;
if (invocation is! InstanceCreationExpression ||
!invocation.isWidgetCreation) {
return null;
}
return grandParent;
}
}
extension ClassElementExtension2 on ClassElement2 {
/// 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 [ClassElement2] that extends the Flutter class
/// `StatefulWidget`.
bool get isStatefulWidgetDeclaration => supertype.isExactlyStatefulWidgetType;
}
extension DartTypeExtension on DartType? {
/// 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.element3.allSupertypes].any((t) =>
t.element3.name3 == 'Color' && t.element3.library2.name3 == '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.element3.allSupertypes].any((t) =>
t.element3.name3 == 'Diagnosticable' &&
t.element3.library2.firstFragment.source.uri == _uriDiagnostics);
}
/// Whether this is the Flutter type `EdgeInsetsGeometry`.
bool get isExactEdgeInsetsGeometryType {
var self = this;
return self is InterfaceType &&
self.element3._isExactly('EdgeInsetsGeometry', _uriEdgeInsets);
}
/// Whether this is the Flutter class `StatefulWidget`.
bool get isExactlyStatefulWidgetType {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameStatefulWidget, _uriFramework);
}
/// Whether this is the Flutter class `StatelessWidget`.
bool get isExactlyStatelessWidgetType {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameStatelessWidget, _uriFramework);
}
/// Whether this is the Flutter class `Align`.
bool get isExactWidgetTypeAlign {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameAlign, _uriBasic);
}
/// Whether this is the Flutter class `StreamBuilder`.
bool get isExactWidgetTypeBuilder {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameBuilder, _uriBasic);
}
/// Whether this is the Flutter class `Center`.
bool get isExactWidgetTypeCenter {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameCenter, _uriBasic);
}
/// Whether this is the Flutter class `Container`.
bool get isExactWidgetTypeContainer {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameContainer, _uriContainer);
}
/// Whether this is the Flutter class `Expanded`.
bool get isExactWidgetTypeExpanded {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameExpanded, _uriBasic);
}
/// Whether this is the Flutter class `Flexible`.
bool get isExactWidgetTypeFlexible {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameFlexible, _uriBasic);
}
/// Whether this is the Flutter class `Padding`.
bool get isExactWidgetTypePadding {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_namePadding, _uriBasic);
}
/// Whether this is the Flutter class `SizedBox`.
bool get isExactWidgetTypeSizedBox {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameSizedBox, _uriBasic);
}
/// Whether this is the Flutter class `StreamBuilder`.
bool get isExactWidgetTypeStreamBuilder {
var self = this;
return self is InterfaceType &&
self.element3._isExactly(_nameStreamBuilder, _uriAsync);
}
/// 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.element3.allSupertypes].any((t) =>
t.element3.name3 == 'Matrix4' &&
t.element3.library2.name3 == 'vector_math_64');
}
/// Whether this is the Flutter class `Widget`, or its subtype.
bool get isWidgetType {
var self = this;
return self is InterfaceType && self.element3.isWidget;
}
}
extension ExpressionExtension on Expression {
/// Whether this is the `builder` argument.
bool get isBuilderArgument {
var self = this;
return self is NamedExpression && self.name.label.name == 'builder';
}
/// Whether this is the `child` argument.
bool get isChildArgument {
var self = this;
return self is NamedExpression && self.name.label.name == 'child';
}
/// Whether this is the `children` argument.
bool get isChildrenArgument {
var self = this;
return self is NamedExpression && self.name.label.name == 'children';
}
/// Whether this is the `sliver` argument.
bool get isSliverArgument {
var self = this;
return self is NamedExpression && self.name.label.name == 'sliver';
}
/// Whether this is the `slivers` argument.
bool get isSliversArgument {
var self = this;
return self is NamedExpression && self.name.label.name == 'slivers';
}
}
extension InstanceCreationExpressionExtension on InstanceCreationExpression {
/// The named expression representing the `builder` argument, or `null` if
/// there is none.
NamedExpression? get builderArgument => argumentList.arguments
.whereType<NamedExpression>()
.firstWhereOrNull((argument) => argument.isBuilderArgument);
/// The named expression representing the `child` argument, or `null` if there
/// is none.
NamedExpression? get childArgument => argumentList.arguments
.whereType<NamedExpression>()
.firstWhereOrNull((argument) => argument.isChildArgument);
/// The named expression representing the `children` argument, or `null` if
/// there is none.
NamedExpression? get childrenArgument => argumentList.arguments
.whereType<NamedExpression>()
.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?.enclosingElement2;
return element.isWidget;
}
/// The named expression representing the `sliver` argument, or `null` if there
/// is none.
NamedExpression? get sliverArgument => argumentList.arguments
.whereType<NamedExpression>()
.firstWhereOrNull((argument) => argument.isSliverArgument);
/// The named expression representing the `slivers` argument, or `null` if
/// there is none.
NamedExpression? get sliversArgument => argumentList.arguments
.whereType<NamedExpression>()
.firstWhereOrNull((argument) => argument.isSliversArgument);
/// The presentation for this node.
String? get widgetPresentationText {
var element = constructorName.element?.enclosingElement2;
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?.name3;
}
}
extension InterfaceElement2Extension on InterfaceElement2? {
/// Whether this is the Flutter class `Flex`, or a subtype.
bool get isFlexWidget {
var self = this;
if (self is! ClassElement2) {
return false;
}
if (!self.isWidget) {
return false;
}
if (_isExactly(_nameFlex, _uriBasic)) {
return true;
}
return self.allSupertypes
.any((type) => type.element3._isExactly(_nameFlex, _uriBasic));
}
}
extension InterfaceElementExtension2 on InterfaceElement2? {
/// 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! ClassElement2) {
return false;
}
if (_isExactly(_nameWidget, _uriFramework)) {
return true;
}
return self.allSupertypes
.any((type) => type.element3._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.element3.name3 == requiredName) {
var uri = type.element3.library2.firstFragment.source.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 ClassElement2 &&
self.name3 == type &&
self.firstFragment.libraryFragment.source.uri == uri;
}
}