diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fdab793..5a7da871 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## 0.4.0 - Added `allow_with_comments` parameter for `no_empty_block` lint. +- Added `exclude_entity` parameter for `prefer_match_file_name` lint. + It is now possible to configure this lint to ignore `enum`, + `extension` and `mixin` declarations via `analysis_options.yaml`. ## 0.3.0 diff --git a/lib/src/common/parameters/excluded_entities_list_parameter.dart b/lib/src/common/parameters/excluded_entities_list_parameter.dart new file mode 100644 index 00000000..34d4277c --- /dev/null +++ b/lib/src/common/parameters/excluded_entities_list_parameter.dart @@ -0,0 +1,52 @@ +import 'package:analyzer/dart/ast/ast.dart'; + +/// A model representing "exclude_entity" parameters for linting, defining +/// identifiers (classes, mixins, enums, extensions) to be ignored during +/// analysis. +/// Supported entities: +/// - mixin +/// - extension +/// - enum +class ExcludedEntitiesListParameter { + /// The parameter model + final Set excludedEntityNames; + + /// A common parameter key for analysis_options.yaml + static const String excludeEntityKey = 'exclude_entity'; + + /// Constructor for [ExcludedEntitiesListParameter] class + ExcludedEntitiesListParameter({ + required this.excludedEntityNames, + }); + + /// Method for creating from json data + factory ExcludedEntitiesListParameter.fromJson(Map json) { + final raw = json['exclude_entity']; + if (raw is List) { + return ExcludedEntitiesListParameter( + excludedEntityNames: Set.from(raw), + ); + } + + return ExcludedEntitiesListParameter( + excludedEntityNames: {}, + ); + } + + /// Returns whether the target node should be ignored during analysis. + bool shouldIgnoreEntity(Declaration node) { + if (excludedEntityNames.isEmpty) return false; + + if (node is MixinDeclaration && excludedEntityNames.contains('mixin')) { + return true; + } else if (node is EnumDeclaration && + excludedEntityNames.contains('enum')) { + return true; + } else if (node is ExtensionDeclaration && + excludedEntityNames.contains('extension')) { + return true; + } + + return false; + } +} diff --git a/lib/src/lints/prefer_match_file_name/models/prefer_match_file_name_parameters.dart b/lib/src/lints/prefer_match_file_name/models/prefer_match_file_name_parameters.dart new file mode 100644 index 00000000..be5a1245 --- /dev/null +++ b/lib/src/lints/prefer_match_file_name/models/prefer_match_file_name_parameters.dart @@ -0,0 +1,19 @@ +import 'package:solid_lints/src/common/parameters/excluded_entities_list_parameter.dart'; + +/// A data model class that represents the "prefer match file name" input +/// parameters +class PreferMatchFileNameParameters { + /// A list of entities that should be excluded from the lint. + final ExcludedEntitiesListParameter excludeEntity; + + /// Constructor for [PreferMatchFileNameParameters] model + const PreferMatchFileNameParameters({ + required this.excludeEntity, + }); + + /// Method for creating from json data + factory PreferMatchFileNameParameters.fromJson(Map json) => + PreferMatchFileNameParameters( + excludeEntity: ExcludedEntitiesListParameter.fromJson(json), + ); +} diff --git a/lib/src/lints/prefer_match_file_name/prefer_match_file_name_rule.dart b/lib/src/lints/prefer_match_file_name/prefer_match_file_name_rule.dart index 26c52e90..66955581 100644 --- a/lib/src/lints/prefer_match_file_name/prefer_match_file_name_rule.dart +++ b/lib/src/lints/prefer_match_file_name/prefer_match_file_name_rule.dart @@ -1,6 +1,7 @@ import 'package:analyzer/error/listener.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:path/path.dart' as p; +import 'package:solid_lints/src/lints/prefer_match_file_name/models/prefer_match_file_name_parameters.dart'; import 'package:solid_lints/src/lints/prefer_match_file_name/visitors/prefer_match_file_name_visitor.dart'; import 'package:solid_lints/src/models/rule_config.dart'; import 'package:solid_lints/src/models/solid_lint_rule.dart'; @@ -48,7 +49,8 @@ import 'package:solid_lints/src/utils/node_utils.dart'; /// class SomethingPublic {} // OK /// ``` /// -class PreferMatchFileNameRule extends SolidLintRule { +class PreferMatchFileNameRule + extends SolidLintRule { /// This lint rule represents the error if iterable /// access can be simplified. static const String lintName = 'prefer_match_file_name'; @@ -62,6 +64,7 @@ class PreferMatchFileNameRule extends SolidLintRule { final config = RuleConfig( configs: configs, name: lintName, + paramsParser: PreferMatchFileNameParameters.fromJson, problemMessage: (value) => 'File name does not match with first declared element name.', ); @@ -76,7 +79,11 @@ class PreferMatchFileNameRule extends SolidLintRule { CustomLintContext context, ) { context.registry.addCompilationUnit((node) { - final visitor = PreferMatchFileNameVisitor(); + final excludedEntities = config.parameters.excludeEntity; + + final visitor = PreferMatchFileNameVisitor( + excludedEntities: excludedEntities, + ); node.accept(visitor); diff --git a/lib/src/lints/prefer_match_file_name/visitors/prefer_match_file_name_visitor.dart b/lib/src/lints/prefer_match_file_name/visitors/prefer_match_file_name_visitor.dart index 3e6024b6..5f008ddd 100644 --- a/lib/src/lints/prefer_match_file_name/visitors/prefer_match_file_name_visitor.dart +++ b/lib/src/lints/prefer_match_file_name/visitors/prefer_match_file_name_visitor.dart @@ -1,5 +1,6 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:solid_lints/src/common/parameters/excluded_entities_list_parameter.dart'; import 'package:solid_lints/src/lints/prefer_match_file_name/models/declaration_token_info.dart'; /// The AST visitor that will collect all Class, Enum, Extension and Mixin @@ -7,14 +8,28 @@ import 'package:solid_lints/src/lints/prefer_match_file_name/models/declaration_ class PreferMatchFileNameVisitor extends RecursiveAstVisitor { final _declarations = []; + /// Iterable that contains the name of entity (or entities) that should + /// be ignored + final ExcludedEntitiesListParameter excludedEntities; + + /// Constructor of [PreferMatchFileNameVisitor] class + PreferMatchFileNameVisitor({ + required this.excludedEntities, + }); + /// List of all declarations - Iterable get declarations => _declarations - ..sort( - // partition into public and private - // put public ones first - // each partition sorted by declaration order - (a, b) => _publicDeclarationsFirst(a, b) ?? _byDeclarationOrder(a, b), - ); + Iterable get declarations => _declarations.where( + (declaration) { + if (declaration.parent is Declaration) { + return !excludedEntities + .shouldIgnoreEntity(declaration.parent as Declaration); + } + return true; + }, + ).toList() + ..sort( + (a, b) => _publicDeclarationsFirst(a, b) ?? _byDeclarationOrder(a, b), + ); @override void visitClassDeclaration(ClassDeclaration node) { diff --git a/lint_test/prefer_match_file_name_ignore_entity/enum/analysis_options.yaml b/lint_test/prefer_match_file_name_ignore_entity/enum/analysis_options.yaml new file mode 100644 index 00000000..c6f0c8a1 --- /dev/null +++ b/lint_test/prefer_match_file_name_ignore_entity/enum/analysis_options.yaml @@ -0,0 +1,9 @@ +analyzer: + plugins: + - ../../../custom_lint + +custom_lint: + rules: + - prefer_match_file_name: + exclude_entity: + - enum diff --git a/lint_test/prefer_match_file_name_ignore_entity/enum/test.dart b/lint_test/prefer_match_file_name_ignore_entity/enum/test.dart new file mode 100644 index 00000000..d73232dd --- /dev/null +++ b/lint_test/prefer_match_file_name_ignore_entity/enum/test.dart @@ -0,0 +1,15 @@ +// ignore_for_file: unused_element, unused_field + +/// `prefer_match_file_name` rule will be ignored by this enttiy because of +/// exclude_entity: +/// - enum +/// in analysis_options.yaml +enum Ignored { _ } + +enum IgnoredAgain { _ } + +// expect_lint: prefer_match_file_name +abstract class WrongNamedClass {} + +/// Only first public element declaration is checked +class PreferMatchFileNameIgnoreExtensions {} diff --git a/lint_test/prefer_match_file_name_ignore_entity/extension/analysis_options.yaml b/lint_test/prefer_match_file_name_ignore_entity/extension/analysis_options.yaml new file mode 100644 index 00000000..8243e1fe --- /dev/null +++ b/lint_test/prefer_match_file_name_ignore_entity/extension/analysis_options.yaml @@ -0,0 +1,9 @@ +analyzer: + plugins: + - ../../../custom_lint + +custom_lint: + rules: + - prefer_match_file_name: + exclude_entity: + - extension diff --git a/lint_test/prefer_match_file_name_ignore_entity/extension/test.dart b/lint_test/prefer_match_file_name_ignore_entity/extension/test.dart new file mode 100644 index 00000000..c4925cfa --- /dev/null +++ b/lint_test/prefer_match_file_name_ignore_entity/extension/test.dart @@ -0,0 +1,15 @@ +// ignore_for_file: unused_element, unused_field + +/// `prefer_match_file_name` rule will be ignored by this extension because of +/// exclude_entity: +/// - extension +/// in analysis_options.yaml +extension Ignored on String {} + +extension IgnoredAgain on String {} + +// expect_lint: prefer_match_file_name +abstract class WrongNamedClass {} + +/// Only first public element declaration is checked +class PreferMatchFileNameIgnoreExtensions {} diff --git a/lint_test/prefer_match_file_name_ignore_entity/mixin/analysis_options.yaml b/lint_test/prefer_match_file_name_ignore_entity/mixin/analysis_options.yaml new file mode 100644 index 00000000..e4206812 --- /dev/null +++ b/lint_test/prefer_match_file_name_ignore_entity/mixin/analysis_options.yaml @@ -0,0 +1,9 @@ +analyzer: + plugins: + - ../../../custom_lint + +custom_lint: + rules: + - prefer_match_file_name: + exclude_entity: + - mixin diff --git a/lint_test/prefer_match_file_name_ignore_entity/mixin/test.dart b/lint_test/prefer_match_file_name_ignore_entity/mixin/test.dart new file mode 100644 index 00000000..70455afc --- /dev/null +++ b/lint_test/prefer_match_file_name_ignore_entity/mixin/test.dart @@ -0,0 +1,13 @@ +// ignore_for_file: unused_element, unused_field + +/// `prefer_match_file_name` rule will be ignored by this entity because of +/// exclude_entity: +/// - mixin +/// in analysis_options.yaml +mixin IgnoredMixin {} + +// expect_lint: prefer_match_file_name +abstract class WrongNamedClass {} + +/// Only first public element declaration is checked +class PreferMatchFileNameIgnoreExtensions {} diff --git a/lint_test/prefer_match_file_name_ignore_extensions.dart b/lint_test/prefer_match_file_name_ignore_extensions.dart new file mode 100644 index 00000000..1af4d330 --- /dev/null +++ b/lint_test/prefer_match_file_name_ignore_extensions.dart @@ -0,0 +1,5 @@ +// ignore_for_file: unused_element, unused_field + +/// Check if the `prefer_match_file_name` rule ignored for extensions +// expect_lint: prefer_match_file_name +extension DefaultExtension on String {}