Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

Commit eea4673

Browse files
committed
feat: add support for mixins and inheritance
1 parent 2c0b82f commit eea4673

File tree

4 files changed

+139
-17
lines changed

4 files changed

+139
-17
lines changed

lib/src/analyzers/lint_analyzer/rules/rules_list/list_all_equatable_fields/list_all_equatable_fields_rule.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import 'package:analyzer/dart/ast/ast.dart';
44
import 'package:analyzer/dart/ast/visitor.dart';
5+
import 'package:analyzer/dart/element/element.dart';
56
import 'package:analyzer/dart/element/type.dart';
67
import 'package:collection/collection.dart';
78

lib/src/analyzers/lint_analyzer/rules/rules_list/list_all_equatable_fields/visitor.dart

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ class _Visitor extends GeneralizingAstVisitor<void> {
88
@override
99
void visitClassDeclaration(ClassDeclaration node) {
1010
final classType = node.extendsClause?.superclass.type;
11-
if (!_isEquatableOrSubclass(classType)) {
11+
12+
final isEquatable = _isEquatableOrSubclass(classType);
13+
final isMixin = node.withClause?.mixinTypes
14+
.any((mixinType) => _isEquatableMixin(mixinType.type)) ??
15+
false;
16+
final isSubclassOfMixin = _isSubclassOfEquatableMixin(classType);
17+
if (!isEquatable && !isMixin && !isSubclassOfMixin) {
1218
return;
1319
}
1420

@@ -20,6 +26,10 @@ class _Visitor extends GeneralizingAstVisitor<void> {
2026
.whereNotNull()
2127
.toSet();
2228

29+
if (isMixin) {
30+
fieldNames.addAll(_getParentFields(classType));
31+
}
32+
2333
final props = node.members.firstWhereOrNull((declaration) =>
2434
declaration is MethodDeclaration &&
2535
declaration.isGetter &&
@@ -29,8 +39,11 @@ class _Visitor extends GeneralizingAstVisitor<void> {
2939
return;
3040
}
3141

32-
final expression = _extractExpression(props.body);
33-
if (expression is ListLiteral) {
42+
final literalVisitor = _ListLiteralVisitor();
43+
props.body.visitChildren(literalVisitor);
44+
45+
final expression = literalVisitor.literals.firstOrNull;
46+
if (expression != null) {
3447
final usedFields = expression.elements
3548
.whereType<SimpleIdentifier>()
3649
.map((identifier) => identifier.name)
@@ -46,20 +59,23 @@ class _Visitor extends GeneralizingAstVisitor<void> {
4659
}
4760
}
4861

49-
Expression? _extractExpression(FunctionBody body) {
50-
if (body is ExpressionFunctionBody) {
51-
return body.expression;
62+
Set<String> _getParentFields(DartType? classType) {
63+
// ignore: deprecated_member_use
64+
final element = classType?.element2;
65+
if (element is! ClassElement) {
66+
return {};
5267
}
5368

54-
if (body is BlockFunctionBody) {
55-
final returnStatement = body.block.statements
56-
.firstWhereOrNull((statement) => statement is ReturnStatement);
57-
if (returnStatement is ReturnStatement) {
58-
return returnStatement.expression;
59-
}
60-
}
61-
62-
return null;
69+
return element.fields
70+
.where(
71+
(field) =>
72+
!field.isStatic &&
73+
!field.isConst &&
74+
!field.isPrivate &&
75+
field.name != 'hashCode',
76+
)
77+
.map((field) => field.name)
78+
.toSet();
6379
}
6480

6581
bool _isEquatableOrSubclass(DartType? type) =>
@@ -70,6 +86,29 @@ class _Visitor extends GeneralizingAstVisitor<void> {
7086

7187
bool _isEquatable(DartType? type) =>
7288
type?.getDisplayString(withNullability: false) == 'Equatable';
89+
90+
bool _isEquatableMixin(DartType? type) =>
91+
// ignore: deprecated_member_use
92+
type?.element2 is MixinElement &&
93+
type?.getDisplayString(withNullability: false) == 'EquatableMixin';
94+
95+
bool _isSubclassOfEquatableMixin(DartType? type) {
96+
// ignore: deprecated_member_use
97+
final element = type?.element2;
98+
99+
return element is ClassElement && element.mixins.any(_isEquatableMixin);
100+
}
101+
}
102+
103+
class _ListLiteralVisitor extends RecursiveAstVisitor<void> {
104+
final literals = <ListLiteral>{};
105+
106+
@override
107+
void visitListLiteral(ListLiteral node) {
108+
super.visitListLiteral(node);
109+
110+
literals.add(node);
111+
}
73112
}
74113

75114
class _DeclarationInfo {

test/src/analyzers/lint_analyzer/rules/rules_list/list_all_equatable_fields/examples/example.dart

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,68 @@ class AndAnotherPerson extends Equatable {
6060
List<Object> get props => [name];
6161
}
6262

63+
class SubPerson extends AndAnotherPerson {
64+
const SubPerson(this.value, String name) : super();
65+
66+
final int value;
67+
68+
@override
69+
List<Object> get props {
70+
return super.props..addAll([]); // LINT
71+
}
72+
}
73+
74+
class EquatableDateTimeSubclass extends EquatableDateTime {
75+
final int century;
76+
77+
EquatableDateTimeSubclass(
78+
this.century,
79+
int year, [
80+
int month = 1,
81+
int day = 1,
82+
int hour = 0,
83+
int minute = 0,
84+
int second = 0,
85+
int millisecond = 0,
86+
int microsecond = 0,
87+
]) : super(year, month, day, hour, minute, second, millisecond, microsecond);
88+
89+
@override
90+
List<Object> get props => super.props..addAll([]); // LINT
91+
}
92+
93+
class EquatableDateTime extends DateTime with EquatableMixin {
94+
EquatableDateTime(
95+
int year, [
96+
int month = 1,
97+
int day = 1,
98+
int hour = 0,
99+
int minute = 0,
100+
int second = 0,
101+
int millisecond = 0,
102+
int microsecond = 0,
103+
]) : super(year, month, day, hour, minute, second, millisecond, microsecond);
104+
105+
@override
106+
List<Object> get props {
107+
return [
108+
year,
109+
month,
110+
day,
111+
hour,
112+
minute,
113+
second,
114+
millisecond,
115+
microsecond,
116+
// LINT
117+
];
118+
}
119+
}
120+
63121
class Equatable {
64122
List<Object> get props;
65123
}
124+
125+
mixin EquatableMixin {
126+
List<Object?> get props;
127+
}

test/src/analyzers/lint_analyzer/rules/rules_list/list_all_equatable_fields/list_all_equatable_fields_rule_test.dart

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,37 @@ void main() {
2525

2626
RuleTestHelper.verifyIssues(
2727
issues: issues,
28-
startLines: [34, 47],
29-
startColumns: [3, 3],
28+
startLines: [34, 47, 69, 90, 106],
29+
startColumns: [3, 3, 3, 3, 3],
3030
locationTexts: [
3131
'List<Object> get props => [name];',
3232
'List<Object> get props {\n'
3333
' return [name]; // LINT\n'
3434
' }',
35+
'List<Object> get props {\n'
36+
' return super.props..addAll([]); // LINT\n'
37+
' }',
38+
'List<Object> get props => super.props..addAll([]);',
39+
'List<Object> get props {\n'
40+
' return [\n'
41+
' year,\n'
42+
' month,\n'
43+
' day,\n'
44+
' hour,\n'
45+
' minute,\n'
46+
' second,\n'
47+
' millisecond,\n'
48+
' microsecond,\n'
49+
' // LINT\n'
50+
' ];\n'
51+
' }',
3552
],
3653
messages: [
3754
'Missing declared class fields: age',
3855
'Missing declared class fields: age, address',
56+
'Missing declared class fields: value',
57+
'Missing declared class fields: century',
58+
'Missing declared class fields: isUtc, millisecondsSinceEpoch, microsecondsSinceEpoch, timeZoneName, timeZoneOffset, weekday',
3959
],
4060
);
4161
});

0 commit comments

Comments
 (0)