diff --git a/lib/main.dart b/lib/main.dart index 546b34b8..f4bb3854 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,6 +20,7 @@ import 'package:solid_lints/src/lints/prefer_first/fixes/prefer_first_fix.dart'; import 'package:solid_lints/src/lints/prefer_first/prefer_first_rule.dart'; import 'package:solid_lints/src/lints/prefer_last/fixes/prefer_last_fix.dart'; import 'package:solid_lints/src/lints/prefer_last/prefer_last_rule.dart'; +import 'package:solid_lints/src/lints/prefer_match_file_name/prefer_match_file_name_rule.dart'; import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart'; import 'package:solid_lints/src/lints/use_nearest_context/fixes/rename_nearest_context_parameter_fix.dart'; import 'package:solid_lints/src/lints/use_nearest_context/use_nearest_context_rule.dart'; @@ -65,6 +66,7 @@ class SolidLintsPlugin extends Plugin { NoMagicNumberRule(analysisOptionsLoader: analysisLoader), preferFirstRule, preferLastRule, + PreferMatchFileNameRule(analysisOptionsLoader: analysisLoader), // TODO: Add more lint rules and use analysisLoader // for rules that need parameters // For example: `CyclomaticComplexityRule(analysisLoader)` diff --git a/lib/src/common/parameters/excluded_entities_list_parameter.dart b/lib/src/common/parameters/excluded_entities_list_parameter.dart index 34d4277c..d62f931a 100644 --- a/lib/src/common/parameters/excluded_entities_list_parameter.dart +++ b/lib/src/common/parameters/excluded_entities_list_parameter.dart @@ -1,11 +1,12 @@ 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 +/// identifiers (classes, mixins, enums, extensions, extension_types) to be ignored during /// analysis. /// Supported entities: /// - mixin /// - extension +/// - extension_type /// - enum class ExcludedEntitiesListParameter { /// The parameter model @@ -20,17 +21,15 @@ class ExcludedEntitiesListParameter { }); /// Method for creating from json data - factory ExcludedEntitiesListParameter.fromJson(Map json) { - final raw = json['exclude_entity']; - if (raw is List) { + factory ExcludedEntitiesListParameter.fromJson(Map json) { + final excludedEntities = json['exclude_entity']; + if (excludedEntities is Iterable) { return ExcludedEntitiesListParameter( - excludedEntityNames: Set.from(raw), + excludedEntityNames: excludedEntities.cast().toSet(), ); } - return ExcludedEntitiesListParameter( - excludedEntityNames: {}, - ); + return ExcludedEntitiesListParameter(excludedEntityNames: {}); } /// Returns whether the target node should be ignored during analysis. @@ -45,6 +44,9 @@ class ExcludedEntitiesListParameter { } else if (node is ExtensionDeclaration && excludedEntityNames.contains('extension')) { return true; + } else if (node is ExtensionTypeDeclaration && + excludedEntityNames.contains('extension_type')) { + 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 index be5a1245..39662156 100644 --- 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 @@ -11,6 +11,13 @@ class PreferMatchFileNameParameters { required this.excludeEntity, }); + /// Empty [PreferMatchFileNameParameters] model. + factory PreferMatchFileNameParameters.empty() { + return PreferMatchFileNameParameters( + excludeEntity: ExcludedEntitiesListParameter(excludedEntityNames: {}), + ); + } + /// Method for creating from json data factory PreferMatchFileNameParameters.fromJson(Map json) => PreferMatchFileNameParameters( 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 c0e075d1..39159a27 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,16 +1,29 @@ -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:analyzer/analysis_rule/rule_context.dart'; +import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; +import 'package:analyzer/error/error.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'; -import 'package:solid_lints/src/utils/node_utils.dart'; /// Warns about a mismatch between file name and first declared element inside. /// /// This improves navigation by matching file content and file name. /// +/// ### Example config: +/// +/// ```yaml +/// plugins: +/// solid_lints: +/// diagnostics: +/// prefer_match_file_name: +/// exclude_entity: +/// - mixin +/// - extension +/// - extension_type +/// - enum +/// ``` +/// /// ## Tests /// /// State: **Disabled**. @@ -51,79 +64,60 @@ import 'package:solid_lints/src/utils/node_utils.dart'; /// class PreferMatchFileNameRule extends SolidLintRule { - /// This lint rule represents the error if iterable - /// access can be simplified. - static const String lintName = 'prefer_match_file_name'; + /// Name of the lint. + static const lintName = 'prefer_match_file_name'; static final _onlySymbolsRegex = RegExp('[^a-zA-Z0-9]'); - PreferMatchFileNameRule._(super.config); + static const _code = LintCode( + lintName, + 'File name does not match with first {0} name.', + ); + + @override + DiagnosticCode get diagnosticCode => _code; /// Creates a new instance of [PreferMatchFileNameRule] /// based on the lint configuration. - factory PreferMatchFileNameRule.createRule(CustomLintConfigs configs) { - final config = RuleConfig( - configs: configs, - name: lintName, - paramsParser: PreferMatchFileNameParameters.fromJson, - problemMessage: (value) => - 'File name does not match with first declared element name.', - ); - - return PreferMatchFileNameRule._(config); - } + PreferMatchFileNameRule({ + required super.analysisOptionsLoader, + }) : super.withParameters( + name: lintName, + description: + 'Warns about a mismatch between file name and first declared ' + 'element inside.', + parametersParser: PreferMatchFileNameParameters.fromJson, + ); @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, ) { - context.registry.addCompilationUnit((node) { - final excludedEntities = config.parameters.excludeEntity; - - final visitor = PreferMatchFileNameVisitor( - excludedEntities: excludedEntities, - ); - - node.accept(visitor); + super.registerNodeProcessors(registry, context); - if (visitor.declarations.isEmpty) return; + final parameters = + getParametersForContext(context) ?? + PreferMatchFileNameParameters.empty(); - final firstDeclaration = visitor.declarations.first; - - if (_doNormalizedNamesMatch( - resolver.source.fullName, - firstDeclaration.token.lexeme, - )) { - return; - } - - final nodeType = - humanReadableNodeType(firstDeclaration.parent).toLowerCase(); + final visitor = PreferMatchFileNameVisitor( + this, + context, + parameters.excludeEntity, + ); - reporter.atToken( - firstDeclaration.token, - LintCode( - name: lintName, - problemMessage: 'File name does not match with first $nodeType name.', - ), - ); - }); + registry.addCompilationUnit(this, visitor); } - bool _doNormalizedNamesMatch(String path, String identifierName) { + /// Checks if the normalized file path matches the normalized identifier name. + bool doNormalizedNamesMatch(String path, String identifierName) { final fileName = _normalizePath(path); final dartIdentifier = _normalizeDartIdentifierName(identifierName); return fileName == dartIdentifier; } - String _normalizePath(String s) => p - .basename(s) - .split('.') - .first - .replaceAll(_onlySymbolsRegex, '') - .toLowerCase(); + String _normalizePath(String s) => + _normalizeDartIdentifierName(p.basename(s).split('.').first); String _normalizeDartIdentifierName(String s) => s.replaceAll(_onlySymbolsRegex, '').toLowerCase(); 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 5f008ddd..8119a81c 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,65 +1,82 @@ +import 'package:analyzer/analysis_rule/rule_context.dart'; 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'; +import 'package:solid_lints/src/lints/prefer_match_file_name/prefer_match_file_name_rule.dart'; +import 'package:solid_lints/src/utils/node_utils.dart'; -/// The AST visitor that will collect all Class, Enum, Extension and Mixin -/// declarations -class PreferMatchFileNameVisitor extends RecursiveAstVisitor { - final _declarations = []; +/// The AST visitor that will collect all Class, Enum, Extension, Mixin and +/// Extension Type declarations +class PreferMatchFileNameVisitor extends SimpleAstVisitor { + /// The lint rule + final PreferMatchFileNameRule rule; + + /// The rule context + final RuleContext context; /// 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.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), - ); + PreferMatchFileNameVisitor( + this.rule, + this.context, + this.excludedEntities, + ); @override - void visitClassDeclaration(ClassDeclaration node) { - super.visitClassDeclaration(node); + void visitCompilationUnit(CompilationUnit node) { + final declarations = []; - _declarations.add((token: node.name, parent: node)); - } + for (final declaration in node.declarations) { + if (excludedEntities.shouldIgnoreEntity(declaration)) { + continue; + } - @override - void visitExtensionDeclaration(ExtensionDeclaration node) { - super.visitExtensionDeclaration(node); + final token = switch (declaration) { + final ClassDeclaration classDecl => classDecl.namePart.typeName, + final ExtensionDeclaration extDecl => extDecl.name, + final MixinDeclaration mixinDecl => mixinDecl.name, + final EnumDeclaration enumDecl => enumDecl.namePart.typeName, + final ExtensionTypeDeclaration extTypeDecl => + extTypeDecl.primaryConstructor.typeName, + _ => null, + }; - final name = node.name; - if (name != null) { - _declarations.add((token: name, parent: node)); + if (token != null) { + declarations.add((token: token, parent: declaration)); + } } - } - @override - void visitMixinDeclaration(MixinDeclaration node) { - super.visitMixinDeclaration(node); + if (declarations.isEmpty) return; - _declarations.add((token: node.name, parent: node)); - } + declarations.sort( + (a, b) => _publicDeclarationsFirst(a, b) ?? _byDeclarationOrder(a, b), + ); - @override - void visitEnumDeclaration(EnumDeclaration node) { - super.visitEnumDeclaration(node); + final firstDeclaration = declarations.first; + final fullName = context.currentUnit?.file.path; + + if (fullName != null && + rule.doNormalizedNamesMatch( + fullName, + firstDeclaration.token.lexeme, + )) { + return; + } - _declarations.add((token: node.name, parent: node)); + final nodeType = humanReadableNodeType( + firstDeclaration.parent, + ).toLowerCase(); + + final reporter = context.currentUnit?.diagnosticReporter; + reporter?.atToken( + firstDeclaration.token, + rule.diagnosticCode, + arguments: [nodeType], + ); } int? _publicDeclarationsFirst( @@ -68,13 +85,12 @@ class PreferMatchFileNameVisitor extends RecursiveAstVisitor { ) { final isAPrivate = Identifier.isPrivateName(a.token.lexeme); final isBPrivate = Identifier.isPrivateName(b.token.lexeme); - if (!isAPrivate && isBPrivate) { - return -1; - } else if (isAPrivate && !isBPrivate) { - return 1; - } - // no reorder needed; - return null; + + return switch ((isAPrivate, isBPrivate)) { + (false, true) => -1, + (true, false) => 1, + _ => null, + }; } int _byDeclarationOrder(DeclarationTokenInfo a, DeclarationTokenInfo b) { diff --git a/lint_test/prefer_match_file_name_enum_test.dart b/lint_test/prefer_match_file_name_enum_test.dart deleted file mode 100644 index 91da9904..00000000 --- a/lint_test/prefer_match_file_name_enum_test.dart +++ /dev/null @@ -1,5 +0,0 @@ -// ignore_for_file: unused_element, unused_field - -/// Check the `prefer_match_file_name` rule -// expect_lint: prefer_match_file_name -enum WrongMixin { a, b } diff --git a/lint_test/prefer_match_file_name_extension_test.dart b/lint_test/prefer_match_file_name_extension_test.dart deleted file mode 100644 index dd5ea1fc..00000000 --- a/lint_test/prefer_match_file_name_extension_test.dart +++ /dev/null @@ -1,5 +0,0 @@ -// ignore_for_file: unused_element, unused_field - -/// Check the `prefer_match_file_name` rule -// expect_lint: prefer_match_file_name -extension WrongExtension on List {} 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 deleted file mode 100644 index c6f0c8a1..00000000 --- a/lint_test/prefer_match_file_name_ignore_entity/enum/analysis_options.yaml +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index d73232dd..00000000 --- a/lint_test/prefer_match_file_name_ignore_entity/enum/test.dart +++ /dev/null @@ -1,15 +0,0 @@ -// 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 deleted file mode 100644 index 8243e1fe..00000000 --- a/lint_test/prefer_match_file_name_ignore_entity/extension/analysis_options.yaml +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index c4925cfa..00000000 --- a/lint_test/prefer_match_file_name_ignore_entity/extension/test.dart +++ /dev/null @@ -1,15 +0,0 @@ -// 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 deleted file mode 100644 index e4206812..00000000 --- a/lint_test/prefer_match_file_name_ignore_entity/mixin/analysis_options.yaml +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 70455afc..00000000 --- a/lint_test/prefer_match_file_name_ignore_entity/mixin/test.dart +++ /dev/null @@ -1,13 +0,0 @@ -// 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 deleted file mode 100644 index 1af4d330..00000000 --- a/lint_test/prefer_match_file_name_ignore_extensions.dart +++ /dev/null @@ -1,5 +0,0 @@ -// 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 {} diff --git a/lint_test/prefer_match_file_name_mixin_test.dart b/lint_test/prefer_match_file_name_mixin_test.dart deleted file mode 100644 index 5b8d2331..00000000 --- a/lint_test/prefer_match_file_name_mixin_test.dart +++ /dev/null @@ -1,5 +0,0 @@ -// ignore_for_file: unused_element, unused_field - -/// Check the `prefer_match_file_name` rule -// expect_lint: prefer_match_file_name -mixin WrongMixin {} diff --git a/lint_test/prefer_match_file_name_test.dart b/lint_test/prefer_match_file_name_test.dart deleted file mode 100644 index 68466050..00000000 --- a/lint_test/prefer_match_file_name_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -// ignore_for_file: unused_element, unused_field - -/// Check the `prefer_match_file_name` rule -class _AnotherPrivateClass {} - -class PreferMatchFileNameTest {} - -/// Only first public element declaration is checked -class WrongClass {} - -class _PrivateClass {} - -extension _PrivateExtension on PreferMatchFileNameTest {} - -enum _PrivateEnum { a, b } - -mixin _PrivateMixin {} diff --git a/lint_test/prefer_match_file_name_wrong_test.dart b/lint_test/prefer_match_file_name_wrong_test.dart deleted file mode 100644 index b0588354..00000000 --- a/lint_test/prefer_match_file_name_wrong_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -// ignore_for_file: unused_element, unused_field - -/// Check the `prefer_match_file_name` rule -class _AnotherPrivateClass {} - -// expect_lint: prefer_match_file_name -class WrongClass {} - -/// Only first public element declaration is checked -class PreferMatchFileNameWrongTest {} diff --git a/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart b/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart new file mode 100644 index 00000000..ff27dfc7 --- /dev/null +++ b/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart @@ -0,0 +1,223 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:analyzer_testing/utilities/utilities.dart'; +import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; +import 'package:solid_lints/src/lints/prefer_match_file_name/prefer_match_file_name_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../../lints/auto_test_lint_offsets.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(PreferMatchFileNameRuleTest); + }); +} + +@reflectiveTest +class PreferMatchFileNameRuleTest extends AnalysisRuleTest + with AutoTestLintOffsets { + static const _excludeEntitiesOptions = ''' +plugins: + solid_lints: + diagnostics: + prefer_match_file_name: + exclude_entity: + - extension + - enum + - mixin + - extension_type +'''; + + @override + void setUp() { + rule = PreferMatchFileNameRule( + analysisOptionsLoader: AnalysisOptionsLoader( + resourceProvider: resourceProvider, + ), + ); + super.setUp(); + } + + void test_does_not_report_when_class_name_matches_file_name() async { + await assertNoDiagnostics(r''' +class Test {} +'''); + } + + void test_reports_when_class_name_does_not_match_file_name() async { + await assertAutoDiagnostics(''' +class ${expectLint('WrongClass')} {} +'''); + } + + void test_does_not_report_on_private_class() async { + await assertNoDiagnostics(r''' +class _PrivateClass {} + +class Test {} +'''); + } + + void test_reports_on_first_public_element_with_mismatch() async { + await assertAutoDiagnostics(''' +class _AnotherPrivateClass {} + +class ${expectLint('WrongClass')} {} + +class PreferMatchFileNameWrongTest {} +'''); + } + + void test_reports_enum_name_mismatch() async { + await assertAutoDiagnostics(''' +enum ${expectLint('WrongEnum')} { a, b } +'''); + } + + void test_reports_mixin_name_mismatch() async { + await assertAutoDiagnostics(''' +mixin ${expectLint('WrongMixin')} {} +'''); + } + + void test_reports_extension_name_mismatch() async { + await assertAutoDiagnostics(''' +extension ${expectLint('WrongExtension')} on List {} +'''); + } + + void test_reports_extension_type_mismatch() async { + await assertAutoDiagnostics(''' +extension type ${expectLint('WrongExtensionType')}(int id) {} +'''); + } + + void test_does_not_report_on_private_enum() async { + await assertNoDiagnostics(r''' +enum _PrivateEnum { a, b } + +class Test {} +'''); + } + + void test_does_not_report_on_private_mixin() async { + await assertNoDiagnostics(r''' +mixin _PrivateMixin {} + +class Test {} +'''); + } + + void test_does_not_report_on_private_extension() async { + await assertNoDiagnostics(r''' +extension _PrivateExtension on String {} + +class Test {} +'''); + } + + void test_does_not_report_on_extension_when_excluded() async { + newAnalysisOptionsYamlFile(testPackageRootPath, ''' +${analysisOptionsContent(rules: [rule.name])} +$_excludeEntitiesOptions'''); + await assertAutoDiagnostics(''' +extension Ignored on String {} + +extension IgnoredAgain on String {} + +abstract class ${expectLint('WrongNamedClass')} {} + +class PreferMatchFileNameIgnoreExtensions {} +'''); + } + + void test_does_not_report_on_enum_when_excluded() async { + newAnalysisOptionsYamlFile(testPackageRootPath, ''' +${analysisOptionsContent(rules: [rule.name])} +$_excludeEntitiesOptions'''); + await assertAutoDiagnostics(''' +enum Ignored { _ } + +enum IgnoredAgain { _ } + +abstract class ${expectLint('WrongNamedClass')} {} + +class PreferMatchFileNameIgnoreExtensions {} +'''); + } + + void test_does_not_report_on_mixin_when_excluded() async { + newAnalysisOptionsYamlFile(testPackageRootPath, ''' +${analysisOptionsContent(rules: [rule.name])} +$_excludeEntitiesOptions'''); + await assertAutoDiagnostics(''' +mixin IgnoredMixin {} + +abstract class ${expectLint('WrongNamedClass')} {} + +class PreferMatchFileNameIgnoreExtensions {} +'''); + } + + void test_does_not_report_on_extension_type_when_excluded() async { + newAnalysisOptionsYamlFile(testPackageRootPath, ''' +${analysisOptionsContent(rules: [rule.name])} +$_excludeEntitiesOptions'''); + await assertAutoDiagnostics(''' +extension type IgnoredExtensionType(int i) {} + +abstract class ${expectLint('WrongNamedClass')} {} + +class PreferMatchFileNameIgnoreExtensions {} +'''); + } + + void test_does_not_report_on_private_extension_type() async { + await assertNoDiagnostics(r''' +extension type _PrivateExtensionType(int id) {} + +class Test {} +'''); + } + + void test_does_not_report_on_unnamed_extension() async { + await assertNoDiagnostics(r''' +extension on String { + void hello() {} +} + +class Test {} +'''); + } + + void test_does_not_report_on_empty_file() async { + await assertNoDiagnostics(r''' +// Only comments +'''); + } + + void test_does_not_report_on_file_with_only_top_level_members() async { + await assertNoDiagnostics(r''' +void someFunction() {} +final someVariable = 42; +'''); + } + + void test_does_not_report_on_multiple_public_declarations_if_first_matches() async { + await assertNoDiagnostics(r''' +class Test {} +class AnotherPublicClass {} +'''); + } + + void test_reports_when_only_private_class_does_not_match_file_name() async { + await assertAutoDiagnostics(''' +class ${expectLint('_WrongPrivateClass')} {} +'''); + } + + void test_does_not_report_on_only_private_class_when_matching() async { + await assertNoDiagnostics(r''' +class _Test {} +'''); + } +}