diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c8c46d4..f4ccc394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ ## 1.0.0 - feat!: migrate to analyzer_server_plugin -- refactor: replace `avoid_unnecessary_type_casts` rule with dart analyzer's `unnecessary_cast` +- refactor: replace `avoid_unnecessary_type_casts` rule with dart analyzer's + `unnecessary_cast` - Added `use_nearest_context` rule. +- Added `use_descriptive_names_for_type_parameters` rule. ## 0.3.3 @@ -17,9 +19,9 @@ - Added `allow_with_comments` parameter for `no_empty_block` lint. - Added extension support for `avoid_using_api` -- 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`. +- 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 @@ -30,7 +32,8 @@ - `function_lines_of_code` - `no_empty_bloc` - `number_of_parameters` -- BREAKING CHANGE: Renamed `excludeNames` parameter to `exclude` for `function_lines_of_code` lint. +- BREAKING CHANGE: Renamed `excludeNames` parameter to `exclude` for + `function_lines_of_code` lint. - Fixed an issue with `prefer_early_retrun` for throw expression - `number_of_parameters` lint: added `copyWith` to the default exclude list. - Update dependencies: @@ -53,28 +56,36 @@ ## 0.2.0 - Added `avoid_final_with_getter` rule -- Improve `avoid_late_keyword` - `ignored_types` to support ignoring subtype of the node type () -- Abstract methods should be omitted by `proper_super_calls` () -- Add a rule prefer_guard_clause for reversing nested if statements () -- add exclude params support to avoid_returning_widgets rule () -- add quick fix to avoid_final_with_getter () +- Improve `avoid_late_keyword` - `ignored_types` to support ignoring subtype of + the node type () +- Abstract methods should be omitted by `proper_super_calls` + () +- Add a rule prefer_guard_clause for reversing nested if statements + () +- add exclude params support to avoid_returning_widgets rule + () +- add quick fix to avoid_final_with_getter + () - Renamed `avoid_debug_print` to `avoid_debug_print_in_release` -- The `avoid_debug_print_in_release` no longer reports a warning if the `debugPrint` call is wrapped in a `!kReleaseMode` check. +- The `avoid_debug_print_in_release` no longer reports a warning if the + `debugPrint` call is wrapped in a `!kReleaseMode` check. - Update custom_lints to work with newer Flutter ## 0.1.5 - Added `avoid_debug_print` rule - Fixed an issue with no_magic_number lint -- Fixed `avoid_unused_parameters` to report positional parameters from typedef if their name are not underscores. +- Fixed `avoid_unused_parameters` to report positional parameters from typedef + if their name are not underscores. - Improvement for `avoid_returning_widget` lint: - ignores methods that override ones that return widget (build() for example) - no longer allows returning widgets from methods/functions named build - Fixed unexpected avoid_unnecessary_type_assertions - Added `excludeNames` param for `function_lines_of_code` lint - Improved `avoid_unrelated_type_assertions` to support true and false results -- Set default `cyclomatic_complexity` to 10 () - Credits: Arthur Miranda () +- Set default `cyclomatic_complexity` to 10 + () Credits: Arthur + Miranda () ## 0.1.4 @@ -108,8 +119,7 @@ - avoid_unnecessary_type_casts - avoid_unrelated_type_assertions - avoid_unused_parameters - - avoid_using_api - Credits: getBoolean () + - avoid_using_api Credits: getBoolean () - cyclomatic_complexity - double_literal_format - function_lines_of_code @@ -176,7 +186,8 @@ ## 0.0.13 -- enable use_colored_box & use_decorated_box + add more comments about specific lints +- enable use_colored_box & use_decorated_box + add more comments about specific + lints ## 0.0.12 diff --git a/lib/analysis_options.yaml b/lib/analysis_options.yaml index dd2a9c95..9dbf6bad 100644 --- a/lib/analysis_options.yaml +++ b/lib/analysis_options.yaml @@ -103,6 +103,7 @@ solid_lints: proper_super_calls: true named_parameters_ordering: true use_nearest_context: true + use_descriptive_names_for_type_parameters: true linter: rules: diff --git a/lib/main.dart b/lib/main.dart index 43c08108..6910da4c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -33,6 +33,7 @@ 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/proper_super_calls/proper_super_calls_rule.dart'; +import 'package:solid_lints/src/lints/use_descriptive_names_for_type_parameters/use_descriptive_names_for_type_parameters_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'; @@ -80,6 +81,9 @@ class SolidLintsPlugin extends Plugin { CyclomaticComplexityRule(analysisOptionsLoader: analysisLoader), NoEmptyBlockRule(analysisOptionsLoader: analysisLoader), UseNearestContextRule(), + UseDescriptiveNamesForTypeParametersRule( + analysisOptionsLoader: analysisLoader, + ), NoMagicNumberRule(analysisOptionsLoader: analysisLoader), preferFirstRule, preferConditionalExpressionsRule, diff --git a/lib/src/lints/use_descriptive_names_for_type_parameters/models/use_descriptive_names_for_type_parameters_parameters.dart b/lib/src/lints/use_descriptive_names_for_type_parameters/models/use_descriptive_names_for_type_parameters_parameters.dart new file mode 100644 index 00000000..fbb86b87 --- /dev/null +++ b/lib/src/lints/use_descriptive_names_for_type_parameters/models/use_descriptive_names_for_type_parameters_parameters.dart @@ -0,0 +1,28 @@ +/// A data model class that represents the +/// "use descriptive names for type parameters" input parameters. +class UseDescriptiveNamesForTypeParametersParameters { + static const _defaultMinTypeParameters = 3; + + /// Minimum number of type parameters required before the rule is enforced. + final int minTypeParameters; + + /// Constructor for [UseDescriptiveNamesForTypeParametersParameters] model + const UseDescriptiveNamesForTypeParametersParameters({ + required this.minTypeParameters, + }); + + /// Empty [UseDescriptiveNamesForTypeParametersParameters] model. + factory UseDescriptiveNamesForTypeParametersParameters.empty() { + return const UseDescriptiveNamesForTypeParametersParameters( + minTypeParameters: _defaultMinTypeParameters, + ); + } + + /// Method for creating from json data. + factory UseDescriptiveNamesForTypeParametersParameters.fromJson( + Map json, + ) => UseDescriptiveNamesForTypeParametersParameters( + minTypeParameters: + json['min_type_parameters'] as int? ?? _defaultMinTypeParameters, + ); +} diff --git a/lib/src/lints/use_descriptive_names_for_type_parameters/use_descriptive_names_for_type_parameters_rule.dart b/lib/src/lints/use_descriptive_names_for_type_parameters/use_descriptive_names_for_type_parameters_rule.dart new file mode 100644 index 00000000..f9929c49 --- /dev/null +++ b/lib/src/lints/use_descriptive_names_for_type_parameters/use_descriptive_names_for_type_parameters_rule.dart @@ -0,0 +1,89 @@ +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:solid_lints/src/lints/use_descriptive_names_for_type_parameters/models/use_descriptive_names_for_type_parameters_parameters.dart'; +import 'package:solid_lints/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart'; +import 'package:solid_lints/src/models/solid_lint_rule.dart'; + +/// A `use_descriptive_names_for_type_parameters` rule which +/// warns about single-letter type parameter names when there are +/// three or more type parameters. +/// +/// ### Example: +/// +/// Assuming config: +/// +/// ```yaml +/// plugins: +/// solid_lints: +/// diagnostics: +/// use_descriptive_names_for_type_parameters: +/// min_type_parameters: 3 +/// ``` +/// +/// #### BAD: +/// ```dart +/// class MyClass {} // LINT +/// ``` +/// +/// #### GOOD: +/// ```dart +/// class MyClass {} // OK +/// class MyClass2 {} // OK +/// ``` +class UseDescriptiveNamesForTypeParametersRule + extends SolidLintRule { + /// The lint rule name. + static const lintName = 'use_descriptive_names_for_type_parameters'; + + static const _code = LintCode( + lintName, + 'Type parameters should have descriptive names instead ' + 'of single letters when there are {0} or ' + 'more type parameters.', + ); + + @override + DiagnosticCode get diagnosticCode => _code; + + /// Creates a new instance of [UseDescriptiveNamesForTypeParametersRule]. + UseDescriptiveNamesForTypeParametersRule({ + required super.analysisOptionsLoader, + }) : super.withParameters( + name: lintName, + description: + 'Warns about single-letter type parameter names when there are ' + 'three or more type parameters.', + parametersParser: + UseDescriptiveNamesForTypeParametersParameters.fromJson, + ); + + @override + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, + ) { + super.registerNodeProcessors(registry, context); + + final parameters = + getParametersForContext(context) ?? + UseDescriptiveNamesForTypeParametersParameters.empty(); + + final visitor = UseDescriptiveNamesForTypeParametersVisitor( + this, + parameters, + ); + + registry.addClassDeclaration(this, visitor); + registry.addClassTypeAlias(this, visitor); + registry.addEnumDeclaration(this, visitor); + registry.addFunctionExpression(this, visitor); + registry.addMethodDeclaration(this, visitor); + registry.addGenericTypeAlias(this, visitor); + registry.addFunctionTypeAlias(this, visitor); + registry.addGenericFunctionType(this, visitor); + registry.addExtensionDeclaration(this, visitor); + registry.addMixinDeclaration(this, visitor); + registry.addExtensionTypeDeclaration(this, visitor); + } +} diff --git a/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart b/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart new file mode 100644 index 00000000..0877132b --- /dev/null +++ b/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart @@ -0,0 +1,86 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:solid_lints/src/lints/use_descriptive_names_for_type_parameters/models/use_descriptive_names_for_type_parameters_parameters.dart'; +import 'package:solid_lints/src/lints/use_descriptive_names_for_type_parameters/use_descriptive_names_for_type_parameters_rule.dart'; + +/// A visitor that checks descriptive names for type parameters on declarations. +class UseDescriptiveNamesForTypeParametersVisitor + extends SimpleAstVisitor { + final UseDescriptiveNamesForTypeParametersRule _rule; + final UseDescriptiveNamesForTypeParametersParameters _parameters; + + /// Creates a new instance of [UseDescriptiveNamesForTypeParametersVisitor]. + UseDescriptiveNamesForTypeParametersVisitor(this._rule, this._parameters); + + void _checkAndReport(TypeParameterList? typeParameters) { + if (typeParameters == null || + typeParameters.typeParameters.length < _parameters.minTypeParameters) { + return; + } + + for (final param in typeParameters.typeParameters) { + final name = param.name.lexeme; + if (name.length == 1 && name != '_') { + _rule.reportAtNode( + param, + arguments: [_parameters.minTypeParameters.toString()], + ); + } + } + } + + @override + void visitClassDeclaration(ClassDeclaration node) { + _checkAndReport(node.namePart.typeParameters); + } + + @override + void visitClassTypeAlias(ClassTypeAlias node) { + _checkAndReport(node.typeParameters); + } + + @override + void visitEnumDeclaration(EnumDeclaration node) { + _checkAndReport(node.namePart.typeParameters); + } + + @override + void visitFunctionExpression(FunctionExpression node) { + _checkAndReport(node.typeParameters); + } + + @override + void visitMethodDeclaration(MethodDeclaration node) { + _checkAndReport(node.typeParameters); + } + + @override + void visitGenericTypeAlias(GenericTypeAlias node) { + _checkAndReport(node.typeParameters); + } + + @override + void visitFunctionTypeAlias(FunctionTypeAlias node) { + _checkAndReport(node.typeParameters); + } + + @override + void visitGenericFunctionType(GenericFunctionType node) { + _checkAndReport(node.typeParameters); + } + + @override + void visitExtensionDeclaration(ExtensionDeclaration node) { + _checkAndReport(node.typeParameters); + } + + @override + void visitMixinDeclaration(MixinDeclaration node) { + _checkAndReport(node.typeParameters); + } + + @override + void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) { + _checkAndReport(node.primaryConstructor.typeParameters); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 56c55842..cd4a0f5a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: solid_lints description: Lints for Dart and Flutter based on software industry standards and best practices. -version: 0.3.4 +version: 1.0.0 homepage: https://github.com/solid-software/solid_lints/ documentation: https://solid-software.github.io/solid_lints/docs/intro topics: [lints, linter, lint, analysis, analyzer] diff --git a/test/src/lints/use_descriptive_names_for_type_parameters/use_descriptive_names_for_type_parameters_rule_test.dart b/test/src/lints/use_descriptive_names_for_type_parameters/use_descriptive_names_for_type_parameters_rule_test.dart new file mode 100644 index 00000000..2b27ba46 --- /dev/null +++ b/test/src/lints/use_descriptive_names_for_type_parameters/use_descriptive_names_for_type_parameters_rule_test.dart @@ -0,0 +1,205 @@ +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/use_descriptive_names_for_type_parameters/use_descriptive_names_for_type_parameters_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../../lints/auto_test_lint_offsets.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(UseDescriptiveNamesForTypeParametersRuleTest); + }); +} + +@reflectiveTest +class UseDescriptiveNamesForTypeParametersRuleTest extends AnalysisRuleTest + with AutoTestLintOffsets { + static const _mockAnalysisOptionsContent = ''' +plugins: + solid_lints: + diagnostics: + use_descriptive_names_for_type_parameters: + min_type_parameters: 3 + '''; + + @override + void setUp() { + rule = UseDescriptiveNamesForTypeParametersRule( + analysisOptionsLoader: AnalysisOptionsLoader( + resourceProvider: resourceProvider, + ), + ); + super.setUp(); + + newAnalysisOptionsYamlFile( + testPackageRootPath, + '''${analysisOptionsContent(rules: [rule.name])} +$_mockAnalysisOptionsContent''', + ); + } + + Future test_reports_on_class() async { + await assertAutoDiagnostics(''' +class MyClass< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} +> {} +'''); + } + + Future test_reports_on_class_type_alias() async { + await assertAutoDiagnostics(''' +class Base {} +mixin MyMixin {} +class MyAlias< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} +> = Base with MyMixin; +'''); + } + + Future test_reports_on_enum() async { + await assertAutoDiagnostics(''' +enum MyEnum< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} +> { + a, b +} +'''); + } + + Future test_reports_on_method() async { + await assertAutoDiagnostics(''' +class MyClass { + void myMethod< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} + >() {} +} +'''); + } + + Future test_reports_on_function() async { + await assertAutoDiagnostics(''' +void myFunction< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} +>() {} +'''); + } + + Future test_reports_on_mixin() async { + await assertAutoDiagnostics(''' +mixin MyMixin< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} +> {} +'''); + } + + Future test_reports_on_extension() async { + await assertAutoDiagnostics(''' +extension MyExtension< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} +> on List {} +'''); + } + + Future test_reports_on_generic_type_alias() async { + await assertAutoDiagnostics(''' +typedef MyAlias< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} +> = Map; +'''); + } + + Future test_reports_on_function_type_alias() async { + await assertAutoDiagnostics(''' +typedef void MyFuncAlias< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} +>(T a, U b, V c); +'''); + } + + Future test_reports_on_generic_function_type() async { + await assertAutoDiagnostics(''' +void test() { + final void Function< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} + >(T, U, V) func; +} +'''); + } + + Future test_reports_on_anonymous_function() async { + await assertAutoDiagnostics(''' +void test() { + final func = < + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} + >(T a, U b, V c) => a; +} +'''); + } + + Future test_reports_on_extension_type() async { + await assertAutoDiagnostics(''' +extension type MyExtensionType< + ${expectLint('T')}, + ${expectLint('U')}, + ${expectLint('V')} +>(List list) {} +'''); + } + + Future test_does_not_report_on_descriptive_names() async { + await assertNoDiagnostics(''' +class MyClass {} +'''); + } + + Future test_does_not_report_on_wildcard() async { + await assertNoDiagnostics(''' +class MyClass {} +'''); + } + + Future test_does_not_report_below_threshold() async { + await assertNoDiagnostics(''' +class MyClass {} +'''); + } + + Future test_reports_when_min_type_parameters_is_1() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + '''${analysisOptionsContent(rules: [rule.name])} +plugins: + solid_lints: + diagnostics: + use_descriptive_names_for_type_parameters: + min_type_parameters: 1''', + ); + + await assertAutoDiagnostics(''' +class MyClass<${expectLint('T')}> {} +'''); + } +}