Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add avoid_final_with_getter rule
Browse files Browse the repository at this point in the history
4akloon committed Apr 17, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 7f08163 commit f49be9d
Showing 6 changed files with 171 additions and 7 deletions.
14 changes: 7 additions & 7 deletions lib/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -3,21 +3,21 @@ analyzer:
- custom_lint
exclude:
# General generated files
- '**/*.g.dart'
- '**/*.gr.dart'
- "**/*.g.dart"
- "**/*.gr.dart"

# Flutter
- 'lib/generated_plugin_registrant.dart'
- "lib/generated_plugin_registrant.dart"

# mockito
- '*.mocks.dart'
- '**/*.mocks.dart'
- "*.mocks.dart"
- "**/*.mocks.dart"

# freezed
- '**/*.freezed.dart'
- "**/*.freezed.dart"

# protobuf
- '**/*.pb.dart'
- "**/*.pb.dart"

# test_coverage
- test/.test_coverage.dart
2 changes: 2 additions & 0 deletions lib/solid_lints.dart
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ library solid_metrics;

import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/lints/avoid_debug_print/avoid_debug_print_rule.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/avoid_final_with_getter_rule.dart';
import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart';
import 'package:solid_lints/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
@@ -61,6 +62,7 @@ class _SolidLints extends PluginBase {
PreferMatchFileNameRule.createRule(configs),
ProperSuperCallsRule.createRule(configs),
AvoidDebugPrint.createRule(configs),
AvoidFinalWithGetterRule.createRule(configs),
];

// Return only enabled rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/avoid_final_with_getter_visitor.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';

/// Avoid using final private fields with getters.
///
/// Final private variables used in a pair with a getter
/// must be changed to a final public type without a getter
/// because it is the same as a public field.
///
/// ### Example
///
/// #### BAD:
///
/// ```dart
/// class MyClass {
/// final int _myField = 0;
///
/// int get myField => _myField;
/// }
/// ```
///
/// #### GOOD:
///
/// ```dart
/// class MyClass {
/// final int myField = 0;
/// }
/// ```
///
class AvoidFinalWithGetterRule extends SolidLintRule {
/// The [LintCode] of this lint rule that represents
/// the error whether we use final private fields with getters.
static const lintName = 'avoid_final_with_getter';

AvoidFinalWithGetterRule._(super.config);

/// Creates a new instance of [AvoidFinalWithGetterRule]
/// based on the lint configuration.
factory AvoidFinalWithGetterRule.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
configs: configs,
name: lintName,
problemMessage: (_) => 'Avoid final private fields with getters.',
);

return AvoidFinalWithGetterRule._(rule);
}

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addCompilationUnit((node) {
final visitor = AvoidFinalWithGetterVisitor();
node.accept(visitor);

for (final variable in visitor.variables) {
reporter.reportErrorForNode(code, variable);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';

/// A visitor that checks for final private fields with getters.
/// If a final private field has a getter, it is considered as a public field.
class AvoidFinalWithGetterVisitor extends RecursiveAstVisitor<void> {
final _variables = <VariableDeclaration>[];

/// List of final private fields with getters
Iterable<VariableDeclaration> get variables => _variables;

@override
void visitVariableDeclaration(VariableDeclaration node) {
final isPrivate = node.declaredElement?.isPrivate ?? false;
final isFinalPrivate = node.isFinal && isPrivate;

if (!isFinalPrivate) return;

final visitor = _GettersVisitor(node);
node.parent?.parent?.parent?.accept(visitor);

if (visitor.getter != null) {
_variables.add(node);
}
super.visitVariableDeclaration(node);
}
}

class _GettersVisitor extends RecursiveAstVisitor<void> {
final VariableDeclaration variable;
final String fieldName;

MethodDeclaration? getter;

_GettersVisitor(this.variable)
: fieldName = variable.name.toString().replaceFirst('_', '');

@override
void visitMethodDeclaration(MethodDeclaration node) {
final name = node.name.toString();

if (name == fieldName && node.isGetter) {
final nodeId = node.getterReferenceId;

final variableId = variable.variableId;

if (nodeId == variableId) {
getter = node;
}
}
super.visitMethodDeclaration(node);
}
}

extension on MethodDeclaration {
int? get getterReferenceId => switch (body) {
ExpressionFunctionBody(
expression: SimpleIdentifier(
staticElement: Element(
declaration: PropertyAccessorElement(
variable: PropertyInducingElement(id: final int id)
)
)
)
) =>
id,
_ => null,
};
}

extension on VariableDeclaration {
int? get variableId => switch (declaredElement) {
VariableElement(id: final int id) => id,
_ => null,
};
}
1 change: 1 addition & 0 deletions lint_test/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -59,3 +59,4 @@ custom_lint:
- prefer_last
- prefer_match_file_name
- proper_super_calls
- avoid_final_with_getter
17 changes: 17 additions & 0 deletions lint_test/avoid_final_with_getter_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// ignore_for_file: type_annotate_public_apis, prefer_match_file_name, unused_local_variable

/// Check final private field with getter fail
/// `avoid_final_with_getter`
class Fail {
// expect_lint: avoid_final_with_getter
final int _myField = 0;

int get myField => _myField;

int get myField2 => _myField + 1;
}

class Good {
final int myField = 0;
}

0 comments on commit f49be9d

Please sign in to comment.