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 18d908d..d03c5e4 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 @@ -1,9 +1,8 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/error/listener.dart'; -import 'package:collection/collection.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:solid_lints/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart'; +import 'package:solid_lints/src/models/ignored_entities_model/ignored_entities_model.dart'; import 'package:solid_lints/src/models/rule_config.dart'; import 'package:solid_lints/src/models/solid_lint_rule.dart'; import 'package:solid_lints/src/utils/types_utils.dart'; @@ -50,8 +49,7 @@ import 'package:solid_lints/src/utils/types_utils.dart'; /// } /// } /// ``` -class AvoidReturningWidgetsRule - extends SolidLintRule { +class AvoidReturningWidgetsRule extends SolidLintRule { /// The [LintCode] of this lint rule that represents /// the error whether we return a widget. static const lintName = 'avoid_returning_widgets'; @@ -64,7 +62,7 @@ class AvoidReturningWidgetsRule final rule = RuleConfig( configs: configs, name: lintName, - paramsParser: AvoidReturningWidgetsParameters.fromJson, + paramsParser: IgnoredEntitiesModel.fromJson, problemMessage: (_) => 'Returning a widget from a function is considered an anti-pattern. ' 'Unless you are overriding an existing method, ' @@ -96,7 +94,7 @@ class AvoidReturningWidgetsRule final isWidgetReturned = hasWidgetType(returnType); - final isIgnored = _shouldIgnore(node); + final isIgnored = config.parameters.matchMethod(node); final isOverriden = node.declaredElement?.hasOverride ?? false; @@ -105,25 +103,4 @@ class AvoidReturningWidgetsRule } }); } - - bool _shouldIgnore(Declaration node) { - final methodName = node.declaredElement?.name; - - final excludedItem = config.parameters.exclude - .firstWhereOrNull((e) => e.methodName == methodName); - - if (excludedItem == null) return false; - - final className = excludedItem.className; - - if (className == null || node is! MethodDeclaration) { - return true; - } else { - final classDeclaration = node.thisOrAncestorOfType(); - - if (classDeclaration == null) return false; - - return classDeclaration.name.toString() == className; - } - } } diff --git a/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_exclude.dart b/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_exclude.dart deleted file mode 100644 index f2cd81b..0000000 --- a/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_exclude.dart +++ /dev/null @@ -1,24 +0,0 @@ -/// Model class for AvoidReturningWidgetsExclude parameters -class AvoidReturningWidgetsExclude { - /// The name of the method that should be excluded from the lint. - final String methodName; - - /// The name of the class that should be excluded from the lint. - final String? className; - - /// Constructor for [AvoidReturningWidgetsExclude] model - const AvoidReturningWidgetsExclude({ - required this.methodName, - required this.className, - }); - - /// - factory AvoidReturningWidgetsExclude.fromJson( - Map json, - ) { - return AvoidReturningWidgetsExclude( - methodName: json['method_name'] as String, - className: json['class_name'] as String?, - ); - } -} 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 deleted file mode 100644 index 75012f5..0000000 --- a/lib/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:solid_lints/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_exclude.dart'; - -/// A data model class that represents the "avoid returning widgets" input -/// parameters. -class AvoidReturningWidgetsParameters { - /// A list of methods that should be excluded from the lint. - final List exclude; - - /// Constructor for [AvoidReturningWidgetsParameters] model - AvoidReturningWidgetsParameters({ - required this.exclude, - }); - - /// Method for creating from json data - factory AvoidReturningWidgetsParameters.fromJson(Map json) { - final exclude = []; - - final excludeList = json['exclude'] as Iterable? ?? []; - for (final item in excludeList) { - if (item is Map) { - exclude.add(AvoidReturningWidgetsExclude.fromJson(item)); - } - } - return AvoidReturningWidgetsParameters( - exclude: exclude, - ); - } -} diff --git a/lib/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart b/lib/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart index be52d0e..8a71333 100644 --- a/lib/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart +++ b/lib/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart @@ -1,6 +1,7 @@ import 'package:analyzer/error/listener.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:solid_lints/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart'; +import 'package:solid_lints/src/models/ignored_entities_model/ignored_entities_model.dart'; import 'package:solid_lints/src/models/rule_config.dart'; import 'package:solid_lints/src/models/solid_lint_rule.dart'; @@ -64,7 +65,7 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// }; /// /// ``` -class AvoidUnusedParametersRule extends SolidLintRule { +class AvoidUnusedParametersRule extends SolidLintRule { /// The [LintCode] of this lint rule that represents /// the error whether we use bad formatted double literals. static const String lintName = 'avoid_unused_parameters'; @@ -79,6 +80,7 @@ class AvoidUnusedParametersRule extends SolidLintRule { final rule = RuleConfig( configs: configs, name: lintName, + paramsParser: IgnoredEntitiesModel.fromJson, problemMessage: (_) => 'Parameter is unused.', ); @@ -92,7 +94,7 @@ class AvoidUnusedParametersRule extends SolidLintRule { CustomLintContext context, ) { context.registry.addCompilationUnit((node) { - final visitor = AvoidUnusedParametersVisitor(); + final visitor = AvoidUnusedParametersVisitor(config.parameters); node.accept(visitor); for (final element in visitor.unusedParameters) { diff --git a/lib/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart b/lib/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart index 40b19f2..5b0e1a0 100644 --- a/lib/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart +++ b/lib/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart @@ -25,12 +25,19 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:collection/collection.dart'; +import 'package:solid_lints/src/models/ignored_entities_model/ignored_entities_model.dart'; import 'package:solid_lints/src/utils/node_utils.dart'; import 'package:solid_lints/src/utils/parameter_utils.dart'; /// AST Visitor which finds all is expressions and checks if they are /// unrelated (result always false) class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { + /// AvoidUnusedParametersVisitor constructor + AvoidUnusedParametersVisitor(this.ignoredEntities); + + /// Entities that should be ignored + final IgnoredEntitiesModel ignoredEntities; + final _unusedParameters = []; /// List of unused parameters @@ -48,6 +55,13 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { parameters.parameters.isEmpty) { return; } + + if (node.thisOrAncestorOfType() case final classNode?) { + if (ignoredEntities.matchClass(classNode)) { + return; + } + } + final unused = _getUnusedParameters( node.body, parameters.parameters, @@ -73,6 +87,16 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { return; } + if (ignoredEntities.matchMethod(node)) { + return; + } + + if (node.thisOrAncestorOfType() case final classNode?) { + if (ignoredEntities.matchClass(classNode)) { + return; + } + } + final isTearOff = _usedAsTearOff(node); if (!isOverride(node.metadata) && !isTearOff) { @@ -93,6 +117,12 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { return; } + if (node.thisOrAncestorOfType() case final funcNode?) { + if (ignoredEntities.matchMethod(funcNode)) { + return; + } + } + _unusedParameters.addAll( _filterOutUnderscoresAndNamed( node.body, diff --git a/lib/src/models/ignored_entities_model/ignored_entities_model.dart b/lib/src/models/ignored_entities_model/ignored_entities_model.dart new file mode 100644 index 0000000..75f5b87 --- /dev/null +++ b/lib/src/models/ignored_entities_model/ignored_entities_model.dart @@ -0,0 +1,73 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:solid_lints/src/models/ignored_entities_model/ignored_entity.dart'; + +/// Manages a list of entities (functions/methods/classes) that should be +/// excluded from a lint rule. +/// +/// Example config: +/// ```yaml +/// custom_lint: +/// rules: +/// - : +/// exclude: +/// # excludes a matching method in a matching class +/// - method_name: excludeMethod +/// class_name: ExcludeClass +/// # excludes a matching method anywhere +/// - method_name: excludeFunction +/// # excludes all methods within a matching class +/// - class_name: ExcludeEntireClass +/// ``` +class IgnoredEntitiesModel { + IgnoredEntitiesModel._({required this.entities}); + + /// + factory IgnoredEntitiesModel.fromJson(Map json) { + final entities = []; + final excludeList = json['exclude'] as Iterable? ?? []; + for (final item in excludeList) { + if (item is Map) { + entities.add(IgnoredEntity.fromJson(item)); + } + } + return IgnoredEntitiesModel._(entities: entities); + } + + /// The entities to be ignored + final List entities; + + /// Checks if the entire class should be ignored. + /// Doesn't match if the config specifies a specific function within the class + bool matchClass(ClassDeclaration node) { + final className = node.name.toString(); + + return entities.any((element) { + return element.functionName == null && element.className == className; + }); + } + + /// Checks if the given method/function should be ignored. + bool matchMethod(Declaration node) { + final methodName = node.declaredElement?.name; + + return entities.any((entity) { + if (entity.functionName != methodName) { + return false; + } + + if (entity.className == null) { + return true; + } + + final matchingClass = node.thisOrAncestorMatching((node) { + if (node case final ClassDeclaration classNode) { + return classNode.name.toString() == entity.className; + } + + return false; + }); + + return matchingClass != null; + }); + } +} diff --git a/lib/src/models/ignored_entities_model/ignored_entity.dart b/lib/src/models/ignored_entities_model/ignored_entity.dart new file mode 100644 index 0000000..f647206 --- /dev/null +++ b/lib/src/models/ignored_entities_model/ignored_entity.dart @@ -0,0 +1,25 @@ +/// An entity (method/function/class) to be excluded from lint +class IgnoredEntity { + IgnoredEntity._({ + this.className, + this.functionName, + }); + + /// + factory IgnoredEntity.fromJson(Map json) { + return IgnoredEntity._( + className: json['class_name'] as String?, + functionName: json['method_name'] as String?, + ); + } + + /// Class name + final String? className; + /// Function name + final String? functionName; + + @override + String toString() { + return "$className: $functionName"; + } +} diff --git a/lint_test/analysis_options.yaml b/lint_test/analysis_options.yaml index 0df2be6..fc43b66 100644 --- a/lint_test/analysis_options.yaml +++ b/lint_test/analysis_options.yaml @@ -3,6 +3,7 @@ analyzer: - custom_lint custom_lint: + debug: true, rules: - cyclomatic_complexity: max_complexity: 4 diff --git a/lint_test/avoid_unused_parameters_test/analysis_options.yaml b/lint_test/avoid_unused_parameters_test/analysis_options.yaml new file mode 100644 index 0000000..9333e0f --- /dev/null +++ b/lint_test/avoid_unused_parameters_test/analysis_options.yaml @@ -0,0 +1,12 @@ +analyzer: + plugins: + - ../custom_lint + +custom_lint: + rules: + - avoid_unused_parameters: + exclude: + - method_name: excludeMethod + class_name: ExcludeClass + - method_name: excludeFunction + - class_name: ExcludeEntireClass diff --git a/lint_test/avoid_unused_parameters_test.dart b/lint_test/avoid_unused_parameters_test/avoid_unused_parameters_test.dart similarity index 96% rename from lint_test/avoid_unused_parameters_test.dart rename to lint_test/avoid_unused_parameters_test/avoid_unused_parameters_test.dart index b75e120..7cba127 100644 --- a/lint_test/avoid_unused_parameters_test.dart +++ b/lint_test/avoid_unused_parameters_test/avoid_unused_parameters_test.dart @@ -207,3 +207,13 @@ class UsingConstructorParameterInInitializer { print(_value); } } + +class ExcludeEntireClass { + void foo(int a) {} +} + +void excludeFunction(int a, int b) {} + +class ExcludeClass { + void excludeMethod(int a, int b) {} +}