Skip to content

Commit

Permalink
feat: Add avoid_shrink_wrap_in_list_view rule
Browse files Browse the repository at this point in the history
  • Loading branch information
naipaka committed Jul 29, 2024
1 parent f695826 commit 9108af2
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 0 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"pubspec",
"redeclares",
"subosito",
"Supertypes",
"tearoffs",
"todos",
"unawaited",
Expand Down
2 changes: 2 additions & 0 deletions packages/altive_lints/lib/altive_lints.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'package:custom_lint_builder/custom_lint_builder.dart';

import 'src/lints/avoid_hardcoded_japanese.dart';
import 'src/lints/avoid_shrink_wrap_in_list_view.dart';

PluginBase createPlugin() => _AltivePlugin();

class _AltivePlugin extends PluginBase {
@override
List<LintRule> getLintRules(CustomLintConfigs configs) => [
const AvoidHardcodedJapanese(),
const AvoidShrinkWrapInListView(),
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/listener.dart';
import 'package:collection/collection.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';

import '../utils/types_utils.dart';

class AvoidShrinkWrapInListView extends DartLintRule {
const AvoidShrinkWrapInListView() : super(code: _code);

static const _code = LintCode(
name: 'avoid_shrink_wrap_in_list_view',
problemMessage: 'Avoid using ListView with shrinkWrap, '
'since it might degrade the performance.\n'
'Consider using slivers instead.',
);

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addInstanceCreationExpression((node) {
if (isListViewWidget(node.staticType) &&
_hasShrinkWrap(node) &&
_hasParentList(node)) {
reporter.reportErrorForNode(_code, node);
}
});
}

bool _hasShrinkWrap(InstanceCreationExpression node) =>
node.argumentList.arguments.firstWhereOrNull(
(arg) => arg is NamedExpression && arg.name.label.name == 'shrinkWrap',
) !=
null;

bool _hasParentList(InstanceCreationExpression node) =>
node.thisOrAncestorMatching(
(parent) =>
parent != node &&
parent is InstanceCreationExpression &&
(isListViewWidget(parent.staticType) ||
isColumnWidget(parent.staticType) ||
isRowWidget(parent.staticType)),
) !=
null;
}
147 changes: 147 additions & 0 deletions packages/altive_lints/lib/src/utils/types_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart';

bool hasWidgetType(DartType type) =>
(isWidgetOrSubclass(type) ||
_isIterable(type) ||
_isList(type) ||
_isFuture(type)) &&
!(_isMultiProvider(type) ||
_isSubclassOfInheritedProvider(type) ||
_isIterableInheritedProvider(type) ||
_isListInheritedProvider(type) ||
_isFutureInheritedProvider(type));

bool isIterable(DartType? type) =>
_checkSelfOrSupertypes(type, (t) => t?.isDartCoreIterable ?? false);

bool isNullableType(DartType? type) =>
type?.nullabilitySuffix == NullabilitySuffix.question;

bool isWidgetOrSubclass(DartType? type) =>
_isWidget(type) || _isSubclassOfWidget(type);

bool isRenderObjectOrSubclass(DartType? type) =>
_isRenderObject(type) || _isSubclassOfRenderObject(type);

bool isRenderObjectWidgetOrSubclass(DartType? type) =>
_isRenderObjectWidget(type) || _isSubclassOfRenderObjectWidget(type);

bool isRenderObjectElementOrSubclass(DartType? type) =>
_isRenderObjectElement(type) || _isSubclassOfRenderObjectElement(type);

bool isWidgetStateOrSubclass(DartType? type) =>
_isWidgetState(type) || _isSubclassOfWidgetState(type);

bool isSubclassOfListenable(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isListenable);

bool isListViewWidget(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'ListView';

bool isSingleChildScrollViewWidget(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'SingleChildScrollView';

bool isColumnWidget(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'Column';

bool isRowWidget(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'Row';

bool isPaddingWidget(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'Padding';

bool isBuildContext(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'BuildContext';

bool isGameWidget(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'GameWidget';

bool _checkSelfOrSupertypes(
DartType? type,
bool Function(DartType?) predicate,
) =>
predicate(type) ||
(type is InterfaceType && type.allSupertypes.any(predicate));

bool _isWidget(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'Widget';

bool _isSubclassOfWidget(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isWidget);

// ignore: deprecated_member_use
bool _isWidgetState(DartType? type) => type?.element2?.displayName == 'State';

bool _isSubclassOfWidgetState(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isWidgetState);

bool _isIterable(DartType type) =>
type.isDartCoreIterable &&
type is InterfaceType &&
isWidgetOrSubclass(type.typeArguments.firstOrNull);

bool _isList(DartType type) =>
type.isDartCoreList &&
type is InterfaceType &&
isWidgetOrSubclass(type.typeArguments.firstOrNull);

bool _isFuture(DartType type) =>
type.isDartAsyncFuture &&
type is InterfaceType &&
isWidgetOrSubclass(type.typeArguments.firstOrNull);

bool _isListenable(DartType type) =>
type.getDisplayString(withNullability: false) == 'Listenable';

bool _isRenderObject(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'RenderObject';

bool _isSubclassOfRenderObject(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isRenderObject);

bool _isRenderObjectWidget(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'RenderObjectWidget';

bool _isSubclassOfRenderObjectWidget(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isRenderObjectWidget);

bool _isRenderObjectElement(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'RenderObjectElement';

bool _isSubclassOfRenderObjectElement(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isRenderObjectElement);

bool _isMultiProvider(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'MultiProvider';

bool _isSubclassOfInheritedProvider(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isInheritedProvider);

bool _isInheritedProvider(DartType? type) =>
type != null &&
type
.getDisplayString(withNullability: false)
.startsWith('InheritedProvider<');

bool _isIterableInheritedProvider(DartType type) =>
type.isDartCoreIterable &&
type is InterfaceType &&
_isSubclassOfInheritedProvider(type.typeArguments.firstOrNull);

bool _isListInheritedProvider(DartType type) =>
type.isDartCoreList &&
type is InterfaceType &&
_isSubclassOfInheritedProvider(type.typeArguments.firstOrNull);

bool _isFutureInheritedProvider(DartType type) =>
type.isDartAsyncFuture &&
type is InterfaceType &&
_isSubclassOfInheritedProvider(type.typeArguments.firstOrNull);

bool isIterableOrSubclass(DartType? type) =>
_checkSelfOrSupertypes(type, (t) => t?.isDartCoreIterable ?? false);

bool isListOrSubclass(DartType? type) =>
_checkSelfOrSupertypes(type, (t) => t?.isDartCoreList ?? false);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
const MyWidget({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
Expanded(
// expect_lint: avoid_shrink_wrap_in_list_view
child: ListView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: const <Widget>[
Text('Hello'),
Text('World'),
],
),
),
],
),
);
}
}
2 changes: 2 additions & 0 deletions packages/altive_lints/lint_test/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ dev_dependencies:
altive_lints:
path: ../
custom_lint: ^0.6.4
flutter:
sdk: flutter
1 change: 1 addition & 0 deletions packages/altive_lints/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ environment:

dependencies:
analyzer: ^6.4.1
collection: ^1.18.0
custom_lint_builder: ^0.6.4

0 comments on commit 9108af2

Please sign in to comment.