diff --git a/example/all.yaml b/example/all.yaml index 9de26ace3..191b82b63 100644 --- a/example/all.yaml +++ b/example/all.yaml @@ -158,6 +158,7 @@ linter: - slash_for_doc_comments - sort_child_properties_last - sort_constructors_first + - sort_private_methods_last - sort_pub_dependencies - sort_unnamed_constructors_first - test_types_in_equals diff --git a/lib/src/rules.dart b/lib/src/rules.dart index dfa824042..e4192c70c 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -163,6 +163,7 @@ import 'rules/sized_box_shrink_expand.dart'; import 'rules/slash_for_doc_comments.dart'; import 'rules/sort_child_properties_last.dart'; import 'rules/sort_constructors_first.dart'; +import 'rules/sort_private_methods_last.dart'; import 'rules/sort_unnamed_constructors_first.dart'; import 'rules/super_goes_last.dart'; import 'rules/test_types_in_equals.dart'; @@ -381,6 +382,7 @@ void registerLintRules({bool inTestMode = false}) { ..register(SortChildPropertiesLast()) ..register(SortConstructorsFirst()) ..register(SortPubDependencies()) + ..register(SortPrivateMethodsLast()) ..register(SortUnnamedConstructorsFirst()) ..register(SuperGoesLast()) ..register(TestTypesInEquals()) diff --git a/lib/src/rules/sort_private_methods_last.dart b/lib/src/rules/sort_private_methods_last.dart new file mode 100644 index 000000000..d97c202d0 --- /dev/null +++ b/lib/src/rules/sort_private_methods_last.dart @@ -0,0 +1,102 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +import '../analyzer.dart'; + +const _desc = r'Define private methods below public methods.'; + +const _details = r''' +**DO** define private methods below public methods. + +This makes the code uniform across multiple classes +and it makes it faster to find specific methods in a class. + +**BAD:** +```dart +class A { + int a() => 0; + int _b() => 0; + int c() => 0; +} +``` + +**GOOD:** +```dart +class A { + int a() => 0; + int c() => 0; + int _b() => 0; +} +``` + +'''; + +class SortPrivateMethodsLast extends LintRule { + SortPrivateMethodsLast() + : super( + name: 'sort_private_methods_last', + description: _desc, + details: _details, + group: Group.style); + + @override + void registerNodeProcessors( + NodeLintRegistry registry, LinterContext context) { + var visitor = _Visitor(this); + registry.addClassDeclaration(this, visitor); + registry.addEnumDeclaration(this, visitor); + registry.addMixinDeclaration(this, visitor); + registry.addExtensionDeclaration(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + + _Visitor(this.rule); + + void check(NodeList members) { + bool foundPrivateMethod = false; + // Members are sorted by source position in the AST. + for (var member in members) { + if (member is MethodDeclaration && + member.modifierKeyword?.keyword != Keyword.STATIC) { + if (foundPrivateMethod && !_isPrivateName(member: member)) { + rule.reportLint(member.parent); + return; + } + if (_isPrivateName(member: member)) { + foundPrivateMethod = true; + } + } + } + } + + bool _isPrivateName({required MethodDeclaration member}) => + Identifier.isPrivateName(member.name.toString()); + + @override + void visitClassDeclaration(ClassDeclaration node) { + check(node.members); + } + + @override + void visitEnumDeclaration(EnumDeclaration node) { + check(node.members); + } + + @override + void visitMixinDeclaration(MixinDeclaration node) { + check(node.members); + } + + @override + void visitExtensionDeclaration(ExtensionDeclaration node) { + check(node.members); + } +} diff --git a/test/rules/sort_private_methods_last.dart b/test/rules/sort_private_methods_last.dart new file mode 100644 index 000000000..bb4b90615 --- /dev/null +++ b/test/rules/sort_private_methods_last.dart @@ -0,0 +1,229 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../rule_test_support.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(SortPrivateMethodsLastTest); + }); +} + +@reflectiveTest +class SortPrivateMethodsLastTest extends LintRuleTest { + @override + String get lintRule => 'sort_private_methods_last'; + + test_public_methods_before_private_methods_in_class() async { + await assertNoDiagnostics(r''' +class A { + int a() => 0; + int b() => _c(); + int _c() => 0; +} +'''); + } + + test_multiple_public_methods_before_private_methods_in_class() async { + await assertNoDiagnostics(r''' +class A { + int a() => 0; + int b() => _c(); + int _c() => _d(); + int _d() => 0; +} +'''); + } + + test_public_method_is_after_private_method_in_class() async { + await assertDiagnostics(r''' +class A { + int a() => 0; + int b() => _c(); + int _c() => d(); + int d() => 0; +} +''', [ + lint(0, 81), + ]); + } + + test_only_public_methods_are_valid_in_class() async { + await assertNoDiagnostics(r''' +class A { + int a() => 0; + int b() => 0; +} +'''); + } + + test_only_private_methods_are_valid_in_class() async { + await assertNoDiagnostics(r''' +class A { + int _a() => _b(); + int _b() => _a(); +} +'''); + } + + test_static_methods_are_ignored() async { + await assertNoDiagnostics(r''' +class A { + int a() => _e(); + int _b() => _c(); + int _c() => _b(); + static int d() => 0; + static int _e() => 0; +} +'''); + } + + test_static_methods_are_ignored_2() async { + await assertNoDiagnostics(r''' +class A { + int a() => _e(); + static int _e() => 0; + int _b() => _c(); + int _c() => _b(); + static int d() => 0; +} +'''); + } + + test_rule_does_not_apply_to_nested_methods() async { + await assertNoDiagnostics(r''' +class A { + int a() { + int b() => 0; + int _c() => b(); + int _d() => _c(); + int e() => _d(); + return e(); + } +} +'''); + } + + test_multiple_cases_are_reported_simultaneously() async { + await assertDiagnostics(r''' +class A { + int _a() => 0; + int b() => _a(); + int _d() => _e(); + int c() => _d(); + int _e() => _f(); + int _f() => c(); +} +''', [ + lint(0, 125), + ]); + } + + test_public_methods_before_private_methods_in_enum() async { + await assertNoDiagnostics(r''' +enum A { + a,b,c; + int d() => 0; + int e() => _f(); + int _f() => 0; +} +'''); + } + + test_multiple_public_methods_before_private_methods_in_enum() async { + await assertNoDiagnostics(r''' +enum A { + a,b,c; + int d() => 0; + int e() => _f(); + int _f() => _g(); + int _g() => 0; +} +'''); + } + + test_public_method_is_after_private_method_in_enum() async { + await assertDiagnostics(r''' +enum A { + a,b,c; + int d() => 0; + int _f() => _g(); + int e() => _f(); + int _g() => 0; +} +''', [ + lint(0, 91), + ]); + } + + test_public_methods_before_private_methods_in_mixin() async { + await assertNoDiagnostics(r''' +mixin A { + int a() => 0; + int b() => _c(); + int _c() => 0; +} +'''); + } + + test_multiple_public_methods_before_private_methods_in_mixin() async { + await assertNoDiagnostics(r''' +mixin A { + int a() => 0; + int b() => _c(); + int _c() => _d(); + int _d() => 0; +} +'''); + } + + test_public_method_is_after_private_method_in_mixin() async { + await assertDiagnostics(r''' +mixin A { + int a() => 0; + int _c() => _d(); + int b() => _c(); + int _d() => 0; +} +''', [ + lint(0, 83), + ]); + } + + test_public_methods_before_private_methods_in_extension() async { + await assertNoDiagnostics(r''' +extension A on int { + int a() => 0; + int b() => _c(); + int _c() => 0; +} +'''); + } + + test_multiple_public_methods_before_private_methods_in_extension() async { + await assertNoDiagnostics(r''' +extension A on int { + int a() => 0; + int b() => _c(); + int _c() => _d(); + int _d() => 0; +} +'''); + } + + test_public_method_is_after_private_method_in_extension() async { + await assertDiagnostics(r''' +extension A on int { + int a() => 0; + int _c() => _d(); + int b() => _c(); + int _d() => 0; +} +''', [ + lint(0, 94), + ]); + } +}