Skip to content

Commit 8dc2cd5

Browse files
authored
149-new-lint-avoid_final_with_getter (#161)
* add avoid_final_with_getter rule * Update analysis_options.yaml to exclude final fields with getters * Refactor avoid_final_with_getter_rule.dart and avoid_final_with_getter_visitor.dart * Fix incorrect test comments in avoid_final_with_getter_test.dart * change avoid_final_with_getter, now it lints getter, no getter name equality * Refactor avoid_final_with_getter_visitor.dart to remove isStatic property from declaredElement * add more tests to avoid_final_with_getter_test.dart * Add avoid_final_with_getter rule to CHANGELOG.md
1 parent 8db68e6 commit 8dc2cd5

8 files changed

Lines changed: 186 additions & 0 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## 0.2.0
22

3+
- Added `avoid_final_with_getter` rule
34
- Improve `avoid_late_keyword` - `ignored_types` to support ignoring subtype of the node type (https://github.com/solid-software/solid_lints/issues/157)
45
- Abstract methods should be omitted by `proper_super_calls` (https://github.com/solid-software/solid_lints/issues/159)
56

lib/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ custom_lint:
5151
- avoid_unrelated_type_assertions
5252
- avoid_unused_parameters
5353
- avoid_debug_print
54+
- avoid_final_with_getter
5455

5556
- cyclomatic_complexity:
5657
max_complexity: 10

lib/solid_lints.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ library solid_metrics;
22

33
import 'package:custom_lint_builder/custom_lint_builder.dart';
44
import 'package:solid_lints/src/lints/avoid_debug_print/avoid_debug_print_rule.dart';
5+
import 'package:solid_lints/src/lints/avoid_final_with_getter/avoid_final_with_getter_rule.dart';
56
import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart';
67
import 'package:solid_lints/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart';
78
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
@@ -61,6 +62,7 @@ class _SolidLints extends PluginBase {
6162
PreferMatchFileNameRule.createRule(configs),
6263
ProperSuperCallsRule.createRule(configs),
6364
AvoidDebugPrint.createRule(configs),
65+
AvoidFinalWithGetterRule.createRule(configs),
6466
];
6567

6668
// Return only enabled rules
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import 'package:analyzer/error/listener.dart';
2+
import 'package:custom_lint_builder/custom_lint_builder.dart';
3+
import 'package:solid_lints/src/lints/avoid_final_with_getter/visitors/avoid_final_with_getter_visitor.dart';
4+
import 'package:solid_lints/src/models/rule_config.dart';
5+
import 'package:solid_lints/src/models/solid_lint_rule.dart';
6+
7+
/// Avoid using final private fields with getters.
8+
///
9+
/// Final private variables used in a pair with a getter
10+
/// must be changed to a final public type without a getter
11+
/// because it is the same as a public field.
12+
///
13+
/// ### Example
14+
///
15+
/// #### BAD:
16+
///
17+
/// ```dart
18+
/// class MyClass {
19+
/// final int _myField = 0;
20+
///
21+
/// int get myField => _myField;
22+
/// }
23+
/// ```
24+
///
25+
/// #### GOOD:
26+
///
27+
/// ```dart
28+
/// class MyClass {
29+
/// final int myField = 0;
30+
/// }
31+
/// ```
32+
///
33+
class AvoidFinalWithGetterRule extends SolidLintRule {
34+
/// The [LintCode] of this lint rule that represents
35+
/// the error whether we use final private fields with getters.
36+
static const lintName = 'avoid_final_with_getter';
37+
38+
AvoidFinalWithGetterRule._(super.config);
39+
40+
/// Creates a new instance of [AvoidFinalWithGetterRule]
41+
/// based on the lint configuration.
42+
factory AvoidFinalWithGetterRule.createRule(CustomLintConfigs configs) {
43+
final rule = RuleConfig(
44+
configs: configs,
45+
name: lintName,
46+
problemMessage: (_) => 'Avoid final private fields with getters.',
47+
);
48+
49+
return AvoidFinalWithGetterRule._(rule);
50+
}
51+
52+
@override
53+
void run(
54+
CustomLintResolver resolver,
55+
ErrorReporter reporter,
56+
CustomLintContext context,
57+
) {
58+
context.registry.addCompilationUnit((node) {
59+
final visitor = AvoidFinalWithGetterVisitor();
60+
node.accept(visitor);
61+
62+
for (final getter in visitor.getters) {
63+
reporter.reportErrorForNode(code, getter);
64+
}
65+
});
66+
}
67+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
import 'package:analyzer/dart/ast/visitor.dart';
3+
import 'package:analyzer/dart/element/element.dart';
4+
import 'package:solid_lints/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart';
5+
6+
/// A visitor that checks for final private fields with getters.
7+
/// If a final private field has a getter, it is considered as a public field.
8+
class AvoidFinalWithGetterVisitor extends RecursiveAstVisitor<void> {
9+
final _getters = <MethodDeclaration>[];
10+
11+
/// List of getters
12+
Iterable<MethodDeclaration> get getters => _getters;
13+
14+
@override
15+
void visitMethodDeclaration(MethodDeclaration node) {
16+
if (node
17+
case MethodDeclaration(
18+
isGetter: true,
19+
declaredElement: ExecutableElement(
20+
isAbstract: false,
21+
isPublic: true,
22+
)
23+
)) {
24+
final visitor = GetterVariableVisitor(node);
25+
node.parent?.accept(visitor);
26+
27+
if (visitor.hasVariable) {
28+
_getters.add(node);
29+
}
30+
}
31+
super.visitMethodDeclaration(node);
32+
}
33+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
import 'package:analyzer/dart/ast/visitor.dart';
3+
import 'package:analyzer/dart/element/element.dart';
4+
5+
/// A visitor that checks the association of the getter with
6+
/// the final private variable
7+
class GetterVariableVisitor extends RecursiveAstVisitor<void> {
8+
final int? _getterId;
9+
VariableDeclaration? _variable;
10+
11+
/// Creates a new instance of [GetterVariableVisitor]
12+
GetterVariableVisitor(MethodDeclaration getter)
13+
: _getterId = getter.getterReferenceId;
14+
15+
/// Is there a variable associated with the getter
16+
bool get hasVariable => _variable != null;
17+
18+
@override
19+
void visitVariableDeclaration(VariableDeclaration node) {
20+
if (node
21+
case VariableDeclaration(
22+
isFinal: true,
23+
declaredElement: VariableElement(id: final id, isPrivate: true)
24+
) when id == _getterId) {
25+
_variable = node;
26+
}
27+
28+
super.visitVariableDeclaration(node);
29+
}
30+
}
31+
32+
extension on MethodDeclaration {
33+
int? get getterReferenceId => switch (body) {
34+
ExpressionFunctionBody(
35+
expression: SimpleIdentifier(
36+
staticElement: Element(
37+
declaration: PropertyAccessorElement(
38+
variable: PropertyInducingElement(:final id)
39+
)
40+
)
41+
)
42+
) =>
43+
id,
44+
_ => null,
45+
};
46+
}

lint_test/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ custom_lint:
5656
- prefer_last
5757
- prefer_match_file_name
5858
- proper_super_calls
59+
- avoid_final_with_getter
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// ignore_for_file: type_annotate_public_apis, prefer_match_file_name, unused_local_variable
2+
3+
/// Check final private field with getter fail
4+
/// `avoid_final_with_getter`
5+
6+
class Fail {
7+
final int _myField = 0;
8+
9+
// expect_lint: avoid_final_with_getter
10+
int get myField => _myField;
11+
}
12+
13+
class FailOtherName {
14+
final int _myField = 0;
15+
16+
// expect_lint: avoid_final_with_getter
17+
int get myFieldInt => _myField;
18+
}
19+
20+
class FailStatic {
21+
static final int _myField = 0;
22+
23+
// expect_lint: avoid_final_with_getter
24+
static int get myField => _myField;
25+
}
26+
27+
class Skip {
28+
final int _myField = 0;
29+
30+
int get myField => _myField + 1; // it is not a getter for the field
31+
}
32+
33+
class Good {
34+
final int myField = 0;
35+
}

0 commit comments

Comments
 (0)