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

Commit b235f78

Browse files
authored
feat: add static code diagnostic consistent-update-render-object (#1004)
* feat: add static code diagnostic prefer-checking-for-equals-in-render-object-setters * chore: rename the rule * fix: handle not equal check * feat: add static code diagnostic consistent-update-render-object * fix: exlude several edge cases * chore: fix typo
1 parent e773d30 commit b235f78

File tree

10 files changed

+771
-0
lines changed

10 files changed

+771
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
* feat: add static code diagnostic [`check-for-equals-in-render-object-setters`](https://dartcodemetrics.dev/docs/rules/flutter/check-for-equals-in-render-object-setters).
6+
* feat: add static code diagnostic [`consistent-update-render-object`](https://dartcodemetrics.dev/docs/rules/flutter/consistent-update-render-object).
67
* feat: add static code diagnostic [`avoid-redundant-async`](https://dartcodemetrics.dev/docs/rules/common/avoid-redundant-async).
78
* feat: add static code diagnostic [`prefer-correct-test-file-name`](https://dartcodemetrics.dev/docs/rules/common/prefer-correct-test-file-name).
89
* feat: add static code diagnostic [`prefer-iterable-of`](https://dartcodemetrics.dev/docs/rules/common/prefer-iterable-of).

lib/src/analyzers/lint_analyzer/rules/rules_factory.dart

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import 'rules_list/ban_name/ban_name_rule.dart';
3030
import 'rules_list/binary_expression_operand_order/binary_expression_operand_order_rule.dart';
3131
import 'rules_list/check_for_equals_in_render_object_setters/check_for_equals_in_render_object_setters_rule.dart';
3232
import 'rules_list/component_annotation_arguments_ordering/component_annotation_arguments_ordering_rule.dart';
33+
import 'rules_list/consistent_update_render_object/consistent_update_render_object_rule.dart';
3334
import 'rules_list/double_literal_format/double_literal_format_rule.dart';
3435
import 'rules_list/format_comment/format_comment_rule.dart';
3536
import 'rules_list/member_ordering/member_ordering_rule.dart';
@@ -103,6 +104,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
103104
CheckForEqualsInRenderObjectSettersRule.new,
104105
ComponentAnnotationArgumentsOrderingRule.ruleId:
105106
ComponentAnnotationArgumentsOrderingRule.new,
107+
ConsistentUpdateRenderObjectRule.ruleId: ConsistentUpdateRenderObjectRule.new,
106108
DoubleLiteralFormatRule.ruleId: DoubleLiteralFormatRule.new,
107109
FormatCommentRule.ruleId: FormatCommentRule.new,
108110
MemberOrderingRule.ruleId: MemberOrderingRule.new,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// ignore_for_file: public_member_api_docs
2+
3+
import 'package:analyzer/dart/ast/ast.dart';
4+
import 'package:analyzer/dart/ast/visitor.dart';
5+
6+
import '../../../../../utils/flutter_types_utils.dart';
7+
import '../../../../../utils/node_utils.dart';
8+
import '../../../lint_utils.dart';
9+
import '../../../models/internal_resolved_unit_result.dart';
10+
import '../../../models/issue.dart';
11+
import '../../../models/severity.dart';
12+
import '../../models/flutter_rule.dart';
13+
import '../../rule_utils.dart';
14+
15+
part 'visitor.dart';
16+
17+
class ConsistentUpdateRenderObjectRule extends FlutterRule {
18+
static const ruleId = 'consistent-update-render-object';
19+
20+
ConsistentUpdateRenderObjectRule([Map<String, Object> config = const {}])
21+
: super(
22+
id: ruleId,
23+
severity: readSeverity(config, Severity.warning),
24+
excludes: readExcludes(config),
25+
);
26+
27+
@override
28+
Iterable<Issue> check(InternalResolvedUnitResult source) {
29+
final visitor = _Visitor();
30+
31+
source.unit.visitChildren(visitor);
32+
33+
return visitor.declarations
34+
.map((declaration) => createIssue(
35+
rule: this,
36+
location: nodeLocation(node: declaration.node, source: source),
37+
message: declaration.errorMessage,
38+
))
39+
.toList(growable: false);
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
part of 'consistent_update_render_object_rule.dart';
2+
3+
class _Visitor extends GeneralizingAstVisitor<void> {
4+
final _declarations = <_DeclarationInfo>[];
5+
6+
Iterable<_DeclarationInfo> get declarations => _declarations;
7+
8+
@override
9+
void visitClassDeclaration(ClassDeclaration node) {
10+
final classType = node.extendsClause?.superclass.type;
11+
if (!isRenderObjectWidgetOrSubclass(classType)) {
12+
return;
13+
}
14+
15+
final methodsVisitor = _MethodsVisitor();
16+
node.visitChildren(methodsVisitor);
17+
18+
final updateDeclaration = methodsVisitor.updateDeclaration;
19+
final createDeclaration = methodsVisitor.createDeclaration;
20+
21+
if (createDeclaration == null) {
22+
return;
23+
}
24+
25+
final creationVisitor = _CreationVisitor();
26+
createDeclaration.visitChildren(creationVisitor);
27+
28+
final createArgumentsLength =
29+
_getCountableArgumentsLength(creationVisitor.arguments);
30+
if (createArgumentsLength == 0) {
31+
return;
32+
}
33+
34+
if (updateDeclaration == null) {
35+
if (node.abstractKeyword == null) {
36+
_declarations.add(_DeclarationInfo(
37+
node,
38+
'Implementation for updateRenderObject method is absent.',
39+
));
40+
}
41+
42+
return;
43+
}
44+
45+
final propertyAccessVisitor = _PropertyAccessVisitor();
46+
updateDeclaration.visitChildren(propertyAccessVisitor);
47+
48+
if (createArgumentsLength != propertyAccessVisitor.propertyAccess.length) {
49+
_declarations.add(_DeclarationInfo(
50+
updateDeclaration,
51+
"updateRenderObject method doesn't update all parameters, that are set in createRenderObject",
52+
));
53+
}
54+
}
55+
56+
int _getCountableArgumentsLength(List<Expression> arguments) =>
57+
arguments.where(
58+
(argument) {
59+
final expression =
60+
argument is NamedExpression ? argument.expression : argument;
61+
62+
return expression is! NullLiteral &&
63+
!isRenderObjectElementOrSubclass(expression.staticType);
64+
},
65+
).length;
66+
}
67+
68+
class _MethodsVisitor extends GeneralizingAstVisitor<void> {
69+
MethodDeclaration? createDeclaration;
70+
71+
MethodDeclaration? updateDeclaration;
72+
73+
@override
74+
void visitMethodDeclaration(MethodDeclaration node) {
75+
// ignore: deprecated_member_use
76+
final name = node.name.name;
77+
if (name == 'updateRenderObject') {
78+
updateDeclaration = node;
79+
} else if (name == 'createRenderObject') {
80+
createDeclaration = node;
81+
}
82+
}
83+
}
84+
85+
class _CreationVisitor extends RecursiveAstVisitor<void> {
86+
final arguments = <Expression>[];
87+
88+
@override
89+
void visitReturnStatement(ReturnStatement node) {
90+
super.visitReturnStatement(node);
91+
92+
final expression = node.expression;
93+
94+
if (expression is InstanceCreationExpression) {
95+
arguments.addAll(expression.argumentList.arguments);
96+
}
97+
}
98+
99+
@override
100+
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
101+
super.visitExpressionFunctionBody(node);
102+
103+
final expression = node.expression;
104+
105+
if (expression is InstanceCreationExpression) {
106+
arguments.addAll(expression.argumentList.arguments);
107+
}
108+
}
109+
}
110+
111+
class _PropertyAccessVisitor extends RecursiveAstVisitor<void> {
112+
final propertyAccess = <Expression>[];
113+
114+
@override
115+
void visitAssignmentExpression(AssignmentExpression node) {
116+
super.visitAssignmentExpression(node);
117+
118+
final expression = node.leftHandSide;
119+
120+
if (expression is PropertyAccess) {
121+
propertyAccess.add(expression);
122+
} else if (expression is PrefixedIdentifier) {
123+
propertyAccess.add(expression);
124+
}
125+
}
126+
}
127+
128+
class _DeclarationInfo {
129+
final Declaration node;
130+
final String errorMessage;
131+
132+
const _DeclarationInfo(this.node, this.errorMessage);
133+
}

lib/src/utils/flutter_types_utils.dart

+18
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ bool isWidgetOrSubclass(DartType? type) =>
1515
bool isRenderObjectOrSubclass(DartType? type) =>
1616
_isRenderObject(type) || _isSubclassOfRenderObject(type);
1717

18+
bool isRenderObjectWidgetOrSubclass(DartType? type) =>
19+
_isRenderObjectWidget(type) || _isSubclassOfRenderObjectWidget(type);
20+
21+
bool isRenderObjectElementOrSubclass(DartType? type) =>
22+
_isRenderObjectElement(type) || _isSubclassOfRenderObjectElement(type);
23+
1824
bool isWidgetStateOrSubclass(DartType? type) =>
1925
_isWidgetState(type) || _isSubclassOfWidgetState(type);
2026

@@ -70,3 +76,15 @@ bool _isRenderObject(DartType? type) =>
7076

7177
bool _isSubclassOfRenderObject(DartType? type) =>
7278
type is InterfaceType && type.allSupertypes.any(_isRenderObject);
79+
80+
bool _isRenderObjectWidget(DartType? type) =>
81+
type?.getDisplayString(withNullability: false) == 'RenderObjectWidget';
82+
83+
bool _isSubclassOfRenderObjectWidget(DartType? type) =>
84+
type is InterfaceType && type.allSupertypes.any(_isRenderObjectWidget);
85+
86+
bool _isRenderObjectElement(DartType? type) =>
87+
type?.getDisplayString(withNullability: false) == 'RenderObjectElement';
88+
89+
bool _isSubclassOfRenderObjectElement(DartType? type) =>
90+
type is InterfaceType && type.allSupertypes.any(_isRenderObjectElement);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart';
2+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/consistent_update_render_object/consistent_update_render_object_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _correctExamplePath =
8+
'consistent_update_render_object/examples/correct_example.dart';
9+
const _incorrectExamplePath =
10+
'consistent_update_render_object/examples/incorrect_example.dart';
11+
12+
void main() {
13+
group('ConsistentUpdateRenderObjectRule', () {
14+
test('initialization', () async {
15+
final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath);
16+
final issues = ConsistentUpdateRenderObjectRule().check(unit);
17+
18+
RuleTestHelper.verifyInitialization(
19+
issues: issues,
20+
ruleId: 'consistent-update-render-object',
21+
severity: Severity.warning,
22+
);
23+
});
24+
25+
test('reports no issues', () async {
26+
final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath);
27+
final issues = ConsistentUpdateRenderObjectRule().check(unit);
28+
29+
RuleTestHelper.verifyNoIssues(issues);
30+
});
31+
32+
test('reports about found issues', () async {
33+
final unit = await RuleTestHelper.resolveFromFile(_incorrectExamplePath);
34+
final issues = ConsistentUpdateRenderObjectRule().check(unit);
35+
36+
RuleTestHelper.verifyIssues(
37+
issues: issues,
38+
startLines: [42, 52, 98],
39+
startColumns: [3, 1, 3],
40+
locationTexts: [
41+
'void updateRenderObject(BuildContext context, _RenderMenuItem renderObject) {}',
42+
'class ColorFiltered extends SingleChildRenderObjectWidget {\n'
43+
' const ColorFiltered({required this.value});\n'
44+
'\n'
45+
' final int value;\n'
46+
'\n'
47+
' @override\n'
48+
' RenderObject createRenderObject(BuildContext context) =>\n'
49+
' _ColorFilterRenderObject(colorFilter);\n'
50+
'}',
51+
'void updateRenderObject(\n'
52+
' BuildContext context,\n'
53+
' _RenderDecoration renderObject,\n'
54+
' ) {\n'
55+
' renderObject\n'
56+
' ..expands = expands\n'
57+
' ..textDirection = textDirection;\n'
58+
' }',
59+
],
60+
messages: [
61+
"updateRenderObject method doesn't update all parameters, that are set in createRenderObject",
62+
'Implementation for updateRenderObject method is absent.',
63+
"updateRenderObject method doesn't update all parameters, that are set in createRenderObject",
64+
],
65+
);
66+
});
67+
});
68+
}

0 commit comments

Comments
 (0)