Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

Sort public methods before private methods #3799

1 change: 1 addition & 0 deletions example/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions lib/src/rules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -381,6 +382,7 @@ void registerLintRules({bool inTestMode = false}) {
..register(SortChildPropertiesLast())
..register(SortConstructorsFirst())
..register(SortPubDependencies())
..register(SortPrivateMethodsLast())
..register(SortUnnamedConstructorsFirst())
..register(SuperGoesLast())
..register(TestTypesInEquals())
Expand Down
102 changes: 102 additions & 0 deletions lib/src/rules/sort_private_methods_last.dart
Original file line number Diff line number Diff line change
@@ -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<ClassMember> 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);
}
}
229 changes: 229 additions & 0 deletions test/rules/sort_private_methods_last.dart
Original file line number Diff line number Diff line change
@@ -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),
]);
}
}