From 7b1abbb05abe78158c0aa9b087eb697e5b54dcee Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Mon, 31 Aug 2026 15:42:25 +0300 Subject: [PATCH 1/4] feat: introduce IgnoredTypesListParameter for consistent ignored type handling across lint rules --- lib/analysis_options.yaml | 6 +- .../ignored_types_list_parameter.dart | 57 ++++++++++ .../models/avoid_late_keyword_parameters.dart | 10 +- .../visitors/avoid_late_keyword_visitor.dart | 8 +- .../avoid_non_null_assertion_parameters.dart | 27 ++--- .../avoid_non_null_assertion_visitor.dart | 14 +-- .../avoid_returning_widgets_rule.dart | 11 ++ .../avoid_returning_widgets_parameters.dart | 43 ++++++-- .../avoid_returning_widgets_visitor.dart | 22 ++-- lib/src/utils/node_utils.dart | 23 +++- lib/src/utils/types_utils.dart | 13 +-- .../parameters/parameters_parsing_test.dart | 30 +++++ .../avoid_returning_widgets_rule_test.dart | 104 ++++++++++++++++++ 13 files changed, 289 insertions(+), 79 deletions(-) create mode 100644 lib/src/common/parameters/ignored_types_list_parameter.dart diff --git a/lib/analysis_options.yaml b/lib/analysis_options.yaml index 9aab1a56..b2f2cb8f 100644 --- a/lib/analysis_options.yaml +++ b/lib/analysis_options.yaml @@ -47,7 +47,11 @@ solid_lints: ignored_types: - AnimationController avoid_non_null_assertion: true - avoid_returning_widgets: true + avoid_returning_widgets: + ignored_types: + - MultiProvider + - InheritedProvider + - InheritedTheme avoid_similar_names: true avoid_unnecessary_return_variable: true avoid_unnecessary_setstate: true diff --git a/lib/src/common/parameters/ignored_types_list_parameter.dart b/lib/src/common/parameters/ignored_types_list_parameter.dart new file mode 100644 index 00000000..fc549671 --- /dev/null +++ b/lib/src/common/parameters/ignored_types_list_parameter.dart @@ -0,0 +1,57 @@ +import 'package:analyzer/dart/element/type.dart'; +import 'package:collection/collection.dart'; +import 'package:solid_lints/src/utils/types_utils.dart'; + +/// A parameter model representing ignored types for linting. +/// It defines types that indicate when expressions, variables, or return values +/// should be ignored during analysis. +/// +/// @docType String | List | Map +class IgnoredTypesListParameter { + /// The set of ignored type names. + final Set ignoredTypes; + + /// A common parameter key for analysis_options.yaml + static const String ignoredTypesKey = 'ignored_types'; + + /// Constructor for [IgnoredTypesListParameter] class. + const IgnoredTypesListParameter({ + required this.ignoredTypes, + }); + + /// Empty [IgnoredTypesListParameter] model. + factory IgnoredTypesListParameter.empty() => const IgnoredTypesListParameter( + ignoredTypes: {}, + ); + + /// Method for creating from json data. + factory IgnoredTypesListParameter.fromJson(Map json) { + final raw = json[ignoredTypesKey]; + final types = switch (raw) { + final Iterable list => list.whereType().toSet(), + final Map map => map.keys.whereType().toSet(), + final String str => {str}, + _ => const {}, + }; + + return IgnoredTypesListParameter(ignoredTypes: types); + } + + /// Returns whether the target type should be ignored during analysis. + bool shouldIgnore(DartType? type) { + if (type == null || ignoredTypes.isEmpty) return false; + return type.hasIgnoredType(ignoredTypes: ignoredTypes); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is IgnoredTypesListParameter && + const SetEquality().equals( + other.ignoredTypes, + ignoredTypes, + ); + + @override + int get hashCode => const SetEquality().hash(ignoredTypes); +} diff --git a/lib/src/lints/avoid_late_keyword/models/avoid_late_keyword_parameters.dart b/lib/src/lints/avoid_late_keyword/models/avoid_late_keyword_parameters.dart index cc52bb94..0808c7ac 100644 --- a/lib/src/lints/avoid_late_keyword/models/avoid_late_keyword_parameters.dart +++ b/lib/src/lints/avoid_late_keyword/models/avoid_late_keyword_parameters.dart @@ -1,3 +1,5 @@ +import 'package:solid_lints/src/common/parameters/ignored_types_list_parameter.dart'; + /// A data model class that represents the "avoid late keyword" input /// parameters. class AvoidLateKeywordParameters { @@ -24,20 +26,18 @@ class AvoidLateKeywordParameters { /// late ColorTween tween; // OK /// late int colorValue; // LINT /// ``` - final Iterable ignoredTypes; + final IgnoredTypesListParameter ignoredTypes; /// Constructor for [AvoidLateKeywordParameters] model const AvoidLateKeywordParameters({ this.allowInitialized = false, - this.ignoredTypes = const [], + this.ignoredTypes = const IgnoredTypesListParameter(ignoredTypes: {}), }); /// Method for creating from json data factory AvoidLateKeywordParameters.fromJson(Map json) => AvoidLateKeywordParameters( allowInitialized: json['allow_initialized'] as bool? ?? false, - ignoredTypes: List.from( - json['ignored_types'] as Iterable? ?? [], - ), + ignoredTypes: IgnoredTypesListParameter.fromJson(json), ); } diff --git a/lib/src/lints/avoid_late_keyword/visitors/avoid_late_keyword_visitor.dart b/lib/src/lints/avoid_late_keyword/visitors/avoid_late_keyword_visitor.dart index a5511622..fddaf5a5 100644 --- a/lib/src/lints/avoid_late_keyword/visitors/avoid_late_keyword_visitor.dart +++ b/lib/src/lints/avoid_late_keyword/visitors/avoid_late_keyword_visitor.dart @@ -2,7 +2,6 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:solid_lints/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart'; import 'package:solid_lints/src/lints/avoid_late_keyword/models/avoid_late_keyword_parameters.dart'; -import 'package:solid_lints/src/utils/types_utils.dart'; /// Visitor for [AvoidLateKeywordRule]. class AvoidLateKeywordVisitor extends SimpleAstVisitor { @@ -26,8 +25,7 @@ class AvoidLateKeywordVisitor extends SimpleAstVisitor { !(_parameters.allowInitialized && node.initializer != null); bool _hasIgnoredType(VariableDeclaration node) => - node.declaredFragment?.element.type.hasIgnoredType( - ignoredTypes: _parameters.ignoredTypes.toSet(), - ) ?? - false; + _parameters.ignoredTypes.shouldIgnore( + node.declaredFragment?.element.type, + ); } diff --git a/lib/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart b/lib/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart index 2a87deaf..bd021bc8 100644 --- a/lib/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart +++ b/lib/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart @@ -1,3 +1,5 @@ +import 'package:solid_lints/src/common/parameters/ignored_types_list_parameter.dart'; + /// A data model class that represents the "avoid non null assertion" input /// parameters. class AvoidNonNullAssertionParameters { @@ -18,7 +20,7 @@ class AvoidNonNullAssertionParameters { /// Map map; /// map['key']!; // OK /// ``` - final Set ignoredTypes; + final IgnoredTypesListParameter ignoredTypes; /// Constructor for [AvoidNonNullAssertionParameters] model const AvoidNonNullAssertionParameters({ @@ -27,23 +29,14 @@ class AvoidNonNullAssertionParameters { /// Empty [AvoidNonNullAssertionParameters] model, ignores nothing. factory AvoidNonNullAssertionParameters.empty() => - const AvoidNonNullAssertionParameters( - ignoredTypes: {}, + AvoidNonNullAssertionParameters( + ignoredTypes: IgnoredTypesListParameter.empty(), ); /// Method for creating from json data - factory AvoidNonNullAssertionParameters.fromJson(Map json) { - final raw = json['ignored_types']; - final excludeList = switch (raw) { - final Iterable rawList => rawList.whereType().toSet(), - final Map rawMap => - rawMap.keys.whereType().toSet(), - final String rawString => {rawString}, - _ => const {}, - }; - - return AvoidNonNullAssertionParameters( - ignoredTypes: excludeList, - ); - } + factory AvoidNonNullAssertionParameters.fromJson( + Map json, + ) => AvoidNonNullAssertionParameters( + ignoredTypes: IgnoredTypesListParameter.fromJson(json), + ); } diff --git a/lib/src/lints/avoid_non_null_assertion/visitors/avoid_non_null_assertion_visitor.dart b/lib/src/lints/avoid_non_null_assertion/visitors/avoid_non_null_assertion_visitor.dart index 8981d35e..e97f78dc 100644 --- a/lib/src/lints/avoid_non_null_assertion/visitors/avoid_non_null_assertion_visitor.dart +++ b/lib/src/lints/avoid_non_null_assertion/visitors/avoid_non_null_assertion_visitor.dart @@ -1,10 +1,8 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/visitor.dart'; -import 'package:analyzer/dart/element/type.dart'; import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart'; import 'package:solid_lints/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart'; -import 'package:solid_lints/src/utils/types_utils.dart'; /// visitor for [AvoidNonNullAssertionRule] class AvoidNonNullAssertionVisitor extends SimpleAstVisitor { @@ -27,21 +25,11 @@ class AvoidNonNullAssertionVisitor extends SimpleAstVisitor { if (operand is IndexExpression) { final type = operand.target?.staticType; - if (_hasIgnoredType(type)) { + if (_parameters.ignoredTypes.shouldIgnore(type)) { return; } } rule.reportAtNode(node); } - - bool _hasIgnoredType(DartType? type) { - if (type == null) { - return false; - } - - return type.hasIgnoredType( - ignoredTypes: _parameters.ignoredTypes, - ); - } } diff --git a/lib/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart b/lib/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart index 3156af82..404d8879 100644 --- a/lib/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart +++ b/lib/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart @@ -21,6 +21,10 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// solid_lints: /// diagnostics: /// avoid_returning_widgets: +/// ignored_types: +/// - MultiProvider +/// - InheritedProvider +/// - InheritedTheme /// exclude: /// - class_name: MyWidget /// method_name: buildCustomButton @@ -57,6 +61,13 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// return const SizedBox(); /// } /// } +/// +/// // Allowed if MultiProvider / InheritedTheme is in ignored_types: +/// MultiProvider buildProviders(Widget child) => MultiProvider( +/// providers: [], +/// child: child, +/// ); +/// InputDecorationTheme get inputTheme => const InputDecorationTheme(); /// ``` class AvoidReturningWidgetsRule extends SolidLintRule { diff --git a/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart b/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart index 14c27036..cb8f9b6f 100644 --- a/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart +++ b/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart @@ -1,4 +1,5 @@ import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart'; +import 'package:solid_lints/src/common/parameters/ignored_types_list_parameter.dart'; /// A data model class that represents the "avoid returning widgets" input /// parameters. @@ -6,22 +7,42 @@ class AvoidReturningWidgetsParameters { /// A list of methods that should be excluded from the lint. final ExcludedIdentifiersListParameter exclude; + /// Types that would be ignored by avoid-returning-widgets rule. + /// + /// Example: + /// + /// ```yaml + /// solid_lints: + /// diagnostics: + /// avoid_returning_widgets: + /// ignored_types: + /// - MultiProvider + /// - InheritedTheme + /// ``` + /// + /// ```dart + /// MultiProvider providers(Widget child) => MultiProvider(...); // OK + /// ``` + final IgnoredTypesListParameter ignoredTypes; + /// Constructor for [AvoidReturningWidgetsParameters] model - AvoidReturningWidgetsParameters({ + const AvoidReturningWidgetsParameters({ required this.exclude, + required this.ignoredTypes, }); /// Empty [AvoidReturningWidgetsParameters] model, excludes nothing. - factory AvoidReturningWidgetsParameters.empty() { - return AvoidReturningWidgetsParameters( - exclude: ExcludedIdentifiersListParameter(exclude: []), - ); - } + factory AvoidReturningWidgetsParameters.empty() => + AvoidReturningWidgetsParameters( + exclude: ExcludedIdentifiersListParameter(exclude: []), + ignoredTypes: IgnoredTypesListParameter.empty(), + ); /// Method for creating from json data - factory AvoidReturningWidgetsParameters.fromJson(Map json) { - return AvoidReturningWidgetsParameters( - exclude: ExcludedIdentifiersListParameter.defaultFromJson(json), - ); - } + factory AvoidReturningWidgetsParameters.fromJson( + Map json, + ) => AvoidReturningWidgetsParameters( + exclude: ExcludedIdentifiersListParameter.defaultFromJson(json), + ignoredTypes: IgnoredTypesListParameter.fromJson(json), + ); } diff --git a/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart b/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart index e8a1d97c..2997d74e 100644 --- a/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart +++ b/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart @@ -36,10 +36,6 @@ class AvoidReturningWidgetsVisitor extends RecursiveAstVisitor { } void _visitDeclaration(Declaration node) { - if (node is! FunctionDeclaration && node is! MethodDeclaration) { - return; - } - if (node is MethodDeclaration && (!node.isComplete || node.body is EmptyFunctionBody || @@ -56,10 +52,17 @@ class AvoidReturningWidgetsVisitor extends RecursiveAstVisitor { }; if (returnType == null) return; - final isWidgetReturned = isWidgetType(returnType); - if (!isWidgetReturned) return; - - final isIgnored = _parameters.exclude.shouldIgnore(node); + if (!isWidgetType(returnType)) return; + + // Only single-expression returns are checked against ignored types. + // Functions with multiple returns/branches must explicitly specify the + // ignored return type in their signature or be refactored into widgets. + final isIgnored = + _parameters.ignoredTypes.shouldIgnore(returnType) || + _parameters.ignoredTypes.shouldIgnore( + node.singleReturnExpression?.staticType, + ) || + _parameters.exclude.shouldIgnore(node); if (isIgnored) return; if (_isOverridden(node)) return; @@ -89,8 +92,7 @@ class AvoidReturningWidgetsVisitor extends RecursiveAstVisitor { } bool _isOverridden(Declaration node) { - if (node is MethodDeclaration && - node.metadata.any((m) => m.name.name == 'override')) { + if (node is MethodDeclaration && isOverride(node.metadata)) { return true; } diff --git a/lib/src/utils/node_utils.dart b/lib/src/utils/node_utils.dart index 1714b856..eed321cd 100644 --- a/lib/src/utils/node_utils.dart +++ b/lib/src/utils/node_utils.dart @@ -327,11 +327,11 @@ extension ExpressionNullableExtension on Expression? { bool get isThisOrSuper => this is ThisExpression || this is SuperExpression; } -/// Extension on [MethodDeclaration] to provide AST helper getters. -extension MethodDeclarationExtension on MethodDeclaration { - /// Returns the single return expression of a method, or null if the - /// method body has multiple statements or no return expression. - Expression? get singleReturnExpression => switch (body) { +/// Extension on [FunctionBody] to provide AST helper getters. +extension FunctionBodyExtension on FunctionBody { + /// Returns the single return expression of a function body, or null if the + /// body has multiple statements or no return expression. + Expression? get singleReturnExpression => switch (this) { ExpressionFunctionBody(:final expression) => expression, BlockFunctionBody( block: Block(statements: [ReturnStatement(:final expression?)]), @@ -340,3 +340,16 @@ extension MethodDeclarationExtension on MethodDeclaration { _ => null, }; } + +/// Extension on [Declaration] to provide AST helper getters. +extension DeclarationExtension on Declaration { + /// Returns the single return expression of a declaration (method or + /// function), or null if the body has multiple statements or no return + /// expression. + Expression? get singleReturnExpression => switch (this) { + MethodDeclaration(:final body) => body.singleReturnExpression, + FunctionDeclaration(:final functionExpression) => + functionExpression.body.singleReturnExpression, + _ => null, + }; +} diff --git a/lib/src/utils/types_utils.dart b/lib/src/utils/types_utils.dart index 79f94915..f3c163d9 100644 --- a/lib/src/utils/types_utils.dart +++ b/lib/src/utils/types_utils.dart @@ -144,9 +144,7 @@ extension InterfaceElementExt on InterfaceElement { } } -bool isWidgetType(DartType type) => - isWidgetOrSubclass(type) && - !(_isMultiProvider(type) || _isSubclassOfInheritedProvider(type)); +bool isWidgetType(DartType type) => isWidgetOrSubclass(type); bool isIterable(DartType? type) => _checkSelfOrSupertypes(type, (t) => t?.isDartCoreIterable ?? false); @@ -235,15 +233,6 @@ bool _isFlutterType(DartType? type, String name) => bool _isFlutterLibrary(LibraryElement library) => library.uri.scheme == 'package' && library.uri.path.startsWith('flutter/'); -bool _isMultiProvider(DartType? type) => - type?.getDisplayString() == 'MultiProvider'; - -bool _isSubclassOfInheritedProvider(DartType? type) => - type is InterfaceType && type.allSupertypes.any(_isInheritedProvider); - -bool _isInheritedProvider(DartType? type) => - type != null && type.getDisplayString().startsWith('InheritedProvider<'); - bool isIterableOrSubclass(DartType? type) => _checkSelfOrSupertypes(type, (t) => t?.isDartCoreIterable ?? false); diff --git a/test/src/common/parameters/parameters_parsing_test.dart b/test/src/common/parameters/parameters_parsing_test.dart index 75a027c4..f68dd14e 100644 --- a/test/src/common/parameters/parameters_parsing_test.dart +++ b/test/src/common/parameters/parameters_parsing_test.dart @@ -1,6 +1,7 @@ import 'package:solid_lints/src/common/parameters/excluded_annotations_list_parameter.dart'; import 'package:solid_lints/src/common/parameters/excluded_entities_list_parameter.dart'; import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart'; +import 'package:solid_lints/src/common/parameters/ignored_types_list_parameter.dart'; import 'package:test/test.dart'; void main() { @@ -87,4 +88,33 @@ void main() { expect(param.exclude, isEmpty); }); }); + + group('IgnoredTypesListParameter', () { + test('parses list of strings', () { + final param = IgnoredTypesListParameter.fromJson({ + 'ignored_types': ['Map', 'List'], + }); + expect(param.ignoredTypes, containsAll(['Map', 'List'])); + }); + + test('parses single string', () { + final param = IgnoredTypesListParameter.fromJson({ + 'ignored_types': 'Map', + }); + expect(param.ignoredTypes, contains('Map')); + expect(param.ignoredTypes.length, 1); + }); + + test('parses map of keys', () { + final param = IgnoredTypesListParameter.fromJson({ + 'ignored_types': {'Map': true, 'List': false}, + }); + expect(param.ignoredTypes, containsAll(['Map', 'List'])); + }); + + test('parses empty or invalid input', () { + final param = IgnoredTypesListParameter.fromJson({}); + expect(param.ignoredTypes, isEmpty); + }); + }); } diff --git a/test/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule_test.dart b/test/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule_test.dart index f36e64c3..437753ce 100644 --- a/test/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule_test.dart +++ b/test/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule_test.dart @@ -72,6 +72,42 @@ abstract interface class WidgetStateProperty {} class WidgetStateColor extends Color implements WidgetStateProperty {} +abstract class InheritedWidget extends Widget { + const InheritedWidget({super.key = ''}); +} + +abstract class InheritedTheme extends InheritedWidget { + const InheritedTheme({super.key = ''}); +} + +class InputDecorationTheme extends InheritedTheme { + const InputDecorationTheme({super.key = ''}); + + @override + Widget build(BuildContext context) => throw 'unimplemented'; +} + +class AppBarTheme extends InheritedTheme { + const AppBarTheme({super.key = ''}); + + @override + Widget build(BuildContext context) => throw 'unimplemented'; +} + +class MultiProvider extends InheritedWidget { + const MultiProvider({super.key = ''}); + + @override + Widget build(BuildContext context) => throw 'unimplemented'; +} + +class InheritedProvider extends InheritedWidget { + const InheritedProvider({super.key = ''}); + + @override + Widget build(BuildContext context) => throw 'unimplemented'; +} + class DecoratedBox extends Widget { const DecoratedBox({required this.decoration}); @@ -86,6 +122,10 @@ plugins: solid_lints: diagnostics: avoid_returning_widgets: + ignored_types: + - MultiProvider + - InheritedProvider + - InheritedTheme exclude: - class_name: ExcludeWidget method_name: excludeWidgetMethod @@ -381,6 +421,70 @@ class Widget {} class CustomService { Widget createCustomWidget() => Widget(); } +'''); + } + + Future test_does_not_report_on_ignored_types_from_config() async { + await assertNoDiagnostics(''' +$_importFlutterWidgets + +class MyTheme { + InputDecorationTheme get inputDecorationTheme => + const InputDecorationTheme(); + AppBarTheme get appBarTheme => const AppBarTheme(); + InheritedTheme get inheritedTheme => const InputDecorationTheme(); + MultiProvider providers() => const MultiProvider(); + InheritedProvider provider() => const InheritedProvider(); +} +'''); + } + + Future test_reports_when_not_in_ignored_types() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + analysisOptionsContent(rules: [rule.name]), + ); + + await assertAutoDiagnostics(''' +$_importFlutterWidgets + +class MyTheme { + ${expectLint('InputDecorationTheme get inputDecorationTheme => const InputDecorationTheme();')} + ${expectLint('MultiProvider providers() => const MultiProvider();')} +} +'''); + } + + Future + test_does_not_report_on_function_returning_ignored_type_as_widget() async { + await assertNoDiagnostics(''' +$_importFlutterWidgets + +Widget providers(Widget child) => const MultiProvider(); + +class MyClass { + Widget buildTheme() { + return const InputDecorationTheme(); + } + + Widget get themeGetter => const AppBarTheme(); +} +'''); + } + + Future + test_reports_on_function_returning_mixed_ignored_and_non_ignored() async { + await assertAutoDiagnostics(''' +$_importFlutterWidgets + +class MyClass { + ${expectLint('''Widget buildWidget(bool condition) { + if (condition) { + return const MultiProvider(); + } + return const SizedBox(); + }''')} +} '''); } } From 4ee809835734a34a6a59dcee80749b749c191a62 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 1 Sep 2026 08:34:02 +0300 Subject: [PATCH 2/4] refactor: simplify avoid_returning_widgets visitor logic and remove configuration options --- lib/analysis_options.yaml | 6 +----- .../visitors/avoid_returning_widgets_visitor.dart | 3 +-- lib/src/utils/types_utils.dart | 2 -- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/analysis_options.yaml b/lib/analysis_options.yaml index b2f2cb8f..9aab1a56 100644 --- a/lib/analysis_options.yaml +++ b/lib/analysis_options.yaml @@ -47,11 +47,7 @@ solid_lints: ignored_types: - AnimationController avoid_non_null_assertion: true - avoid_returning_widgets: - ignored_types: - - MultiProvider - - InheritedProvider - - InheritedTheme + avoid_returning_widgets: true avoid_similar_names: true avoid_unnecessary_return_variable: true avoid_unnecessary_setstate: true diff --git a/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart b/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart index 2997d74e..6635361b 100644 --- a/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart +++ b/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart @@ -50,9 +50,8 @@ class AvoidReturningWidgetsVisitor extends RecursiveAstVisitor { declaredFragment.element.returnType, _ => null, }; - if (returnType == null) return; - if (!isWidgetType(returnType)) return; + if (!isWidgetOrSubclass(returnType)) return; // Only single-expression returns are checked against ignored types. // Functions with multiple returns/branches must explicitly specify the diff --git a/lib/src/utils/types_utils.dart b/lib/src/utils/types_utils.dart index f3c163d9..73f05528 100644 --- a/lib/src/utils/types_utils.dart +++ b/lib/src/utils/types_utils.dart @@ -144,8 +144,6 @@ extension InterfaceElementExt on InterfaceElement { } } -bool isWidgetType(DartType type) => isWidgetOrSubclass(type); - bool isIterable(DartType? type) => _checkSelfOrSupertypes(type, (t) => t?.isDartCoreIterable ?? false); From e5b13ede4f69c33a413cb5724bfc7cad41e0a50b Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 1 Sep 2026 17:47:32 +0300 Subject: [PATCH 3/4] refactor: implement Equatable for IgnoredTypesListParameter to simplify equality checks --- .../parameters/ignored_types_list_parameter.dart | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/src/common/parameters/ignored_types_list_parameter.dart b/lib/src/common/parameters/ignored_types_list_parameter.dart index fc549671..5e9a31f5 100644 --- a/lib/src/common/parameters/ignored_types_list_parameter.dart +++ b/lib/src/common/parameters/ignored_types_list_parameter.dart @@ -1,5 +1,6 @@ import 'package:analyzer/dart/element/type.dart'; -import 'package:collection/collection.dart'; + +import 'package:equatable/equatable.dart'; import 'package:solid_lints/src/utils/types_utils.dart'; /// A parameter model representing ignored types for linting. @@ -7,7 +8,7 @@ import 'package:solid_lints/src/utils/types_utils.dart'; /// should be ignored during analysis. /// /// @docType String | List | Map -class IgnoredTypesListParameter { +class IgnoredTypesListParameter extends Equatable { /// The set of ignored type names. final Set ignoredTypes; @@ -44,14 +45,5 @@ class IgnoredTypesListParameter { } @override - bool operator ==(Object other) => - identical(this, other) || - other is IgnoredTypesListParameter && - const SetEquality().equals( - other.ignoredTypes, - ignoredTypes, - ); - - @override - int get hashCode => const SetEquality().hash(ignoredTypes); + List get props => [ignoredTypes]; } From 3e0132c58e2da621b10627be1f9aa3f79a0cb982 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 1 Sep 2026 17:57:22 +0300 Subject: [PATCH 4/4] refactor: consolidate avoid_returning_widgets visitor logic into parameter model and node utility extension --- .../ignored_types_list_parameter.dart | 3 ++ .../avoid_returning_widgets_parameters.dart | 11 +++++++ .../avoid_returning_widgets_visitor.dart | 32 ++++--------------- lib/src/utils/node_utils.dart | 10 ++++++ 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/lib/src/common/parameters/ignored_types_list_parameter.dart b/lib/src/common/parameters/ignored_types_list_parameter.dart index 5e9a31f5..32a9c500 100644 --- a/lib/src/common/parameters/ignored_types_list_parameter.dart +++ b/lib/src/common/parameters/ignored_types_list_parameter.dart @@ -44,6 +44,9 @@ class IgnoredTypesListParameter extends Equatable { return type.hasIgnoredType(ignoredTypes: ignoredTypes); } + /// Returns `true` if any of the target [types] should be ignored. + bool shouldIgnoreAny(Iterable types) => types.any(shouldIgnore); + @override List get props => [ignoredTypes]; } diff --git a/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart b/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart index cb8f9b6f..365f110e 100644 --- a/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart +++ b/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart @@ -1,5 +1,7 @@ +import 'package:analyzer/dart/ast/ast.dart'; import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart'; import 'package:solid_lints/src/common/parameters/ignored_types_list_parameter.dart'; +import 'package:solid_lints/src/utils/node_utils.dart'; /// A data model class that represents the "avoid returning widgets" input /// parameters. @@ -45,4 +47,13 @@ class AvoidReturningWidgetsParameters { exclude: ExcludedIdentifiersListParameter.defaultFromJson(json), ignoredTypes: IgnoredTypesListParameter.fromJson(json), ); + + /// Returns `true` if the given [node] should be ignored by the lint rule. + bool shouldIgnore(Declaration node) { + return ignoredTypes.shouldIgnoreAny([ + node.returnType, + node.singleReturnExpression?.staticType, + ]) || + exclude.shouldIgnore(node); + } } diff --git a/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart b/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart index 6635361b..18c31333 100644 --- a/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart +++ b/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart @@ -43,28 +43,11 @@ class AvoidReturningWidgetsVisitor extends RecursiveAstVisitor { return; } - final returnType = switch (node) { - MethodDeclaration(:final declaredFragment?) => - declaredFragment.element.returnType, - FunctionDeclaration(:final declaredFragment?) => - declaredFragment.element.returnType, - _ => null, - }; - - if (!isWidgetOrSubclass(returnType)) return; - - // Only single-expression returns are checked against ignored types. - // Functions with multiple returns/branches must explicitly specify the - // ignored return type in their signature or be refactored into widgets. - final isIgnored = - _parameters.ignoredTypes.shouldIgnore(returnType) || - _parameters.ignoredTypes.shouldIgnore( - node.singleReturnExpression?.staticType, - ) || - _parameters.exclude.shouldIgnore(node); - if (isIgnored) return; - - if (_isOverridden(node)) return; + if (!isWidgetOrSubclass(node.returnType) || + _parameters.shouldIgnore(node) || + _isOverridden(node)) { + return; + } _rule.reportAtNode(node); } @@ -91,11 +74,8 @@ class AvoidReturningWidgetsVisitor extends RecursiveAstVisitor { } bool _isOverridden(Declaration node) { - if (node is MethodDeclaration && isOverride(node.metadata)) { - return true; - } - return switch (node) { + MethodDeclaration(:final metadata) when isOverride(metadata) => true, Declaration( declaredFragment: Fragment( element: Element( diff --git a/lib/src/utils/node_utils.dart b/lib/src/utils/node_utils.dart index eed321cd..95b58b58 100644 --- a/lib/src/utils/node_utils.dart +++ b/lib/src/utils/node_utils.dart @@ -352,4 +352,14 @@ extension DeclarationExtension on Declaration { functionExpression.body.singleReturnExpression, _ => null, }; + + /// Returns the declared return type of a declaration (method or + /// function), or null if none. + DartType? get returnType => switch (this) { + MethodDeclaration(:final declaredFragment?) => + declaredFragment.element.returnType, + FunctionDeclaration(:final declaredFragment?) => + declaredFragment.element.returnType, + _ => null, + }; }