-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add
avoid_shrink_wrap_in_list_view
rule
- Loading branch information
Showing
7 changed files
with
228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
"pubspec", | ||
"redeclares", | ||
"subosito", | ||
"Supertypes", | ||
"tearoffs", | ||
"todos", | ||
"unawaited", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
]; | ||
} |
49 changes: 49 additions & 0 deletions
49
packages/altive_lints/lib/src/lints/avoid_shrink_wrap_in_list_view.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
26 changes: 26 additions & 0 deletions
26
packages/altive_lints/lint_test/lints/avoid_shrink_wrap_in_list_view.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'), | ||
], | ||
), | ||
), | ||
], | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,5 @@ dev_dependencies: | |
altive_lints: | ||
path: ../ | ||
custom_lint: ^0.6.4 | ||
flutter: | ||
sdk: flutter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,4 +16,5 @@ environment: | |
|
||
dependencies: | ||
analyzer: ^6.4.1 | ||
collection: ^1.18.0 | ||
custom_lint_builder: ^0.6.4 |