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

feat: add static code diagnostic consistent-update-render-object #1004

Merged
merged 7 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

* 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).
* feat: add static code diagnostic [`consistent-update-render-object`](https://dartcodemetrics.dev/docs/rules/flutter/consistent-update-render-object).
* feat: add static code diagnostic [`avoid-redundant-async`](https://dartcodemetrics.dev/docs/rules/common/avoid-redundant-async).
* feat: add static code diagnostic [`prefer-correct-test-file-name`](https://dartcodemetrics.dev/docs/rules/common/prefer-correct-test-file-name).
* feat: add static code diagnostic [`prefer-iterable-of`](https://dartcodemetrics.dev/docs/rules/common/prefer-iterable-of).
Expand Down
2 changes: 2 additions & 0 deletions lib/src/analyzers/lint_analyzer/rules/rules_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import 'rules_list/ban_name/ban_name_rule.dart';
import 'rules_list/binary_expression_operand_order/binary_expression_operand_order_rule.dart';
import 'rules_list/check_for_equals_in_render_object_setters/check_for_equals_in_render_object_setters_rule.dart';
import 'rules_list/component_annotation_arguments_ordering/component_annotation_arguments_ordering_rule.dart';
import 'rules_list/consistent_update_render_object/consistent_update_render_object_rule.dart';
import 'rules_list/double_literal_format/double_literal_format_rule.dart';
import 'rules_list/format_comment/format_comment_rule.dart';
import 'rules_list/member_ordering/member_ordering_rule.dart';
Expand Down Expand Up @@ -103,6 +104,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
CheckForEqualsInRenderObjectSettersRule.new,
ComponentAnnotationArgumentsOrderingRule.ruleId:
ComponentAnnotationArgumentsOrderingRule.new,
ConsistentUpdateRenderObjectRule.ruleId: ConsistentUpdateRenderObjectRule.new,
DoubleLiteralFormatRule.ruleId: DoubleLiteralFormatRule.new,
FormatCommentRule.ruleId: FormatCommentRule.new,
MemberOrderingRule.ruleId: MemberOrderingRule.new,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// ignore_for_file: public_member_api_docs

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';

import '../../../../../utils/flutter_types_utils.dart';
import '../../../../../utils/node_utils.dart';
import '../../../lint_utils.dart';
import '../../../models/internal_resolved_unit_result.dart';
import '../../../models/issue.dart';
import '../../../models/severity.dart';
import '../../models/flutter_rule.dart';
import '../../rule_utils.dart';

part 'visitor.dart';

class ConsistentUpdateRenderObjectRule extends FlutterRule {
static const ruleId = 'consistent-update-render-object';

ConsistentUpdateRenderObjectRule([Map<String, Object> config = const {}])
: super(
id: ruleId,
severity: readSeverity(config, Severity.warning),
excludes: readExcludes(config),
);

@override
Iterable<Issue> check(InternalResolvedUnitResult source) {
final visitor = _Visitor();

source.unit.visitChildren(visitor);

return visitor.declarations
.map((declaration) => createIssue(
rule: this,
location: nodeLocation(node: declaration.node, source: source),
message: declaration.errorMessage,
))
.toList(growable: false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
part of 'consistent_update_render_object_rule.dart';

class _Visitor extends GeneralizingAstVisitor<void> {
final _declarations = <_DeclarationInfo>[];

Iterable<_DeclarationInfo> get declarations => _declarations;

@override
void visitClassDeclaration(ClassDeclaration node) {
final classType = node.extendsClause?.superclass.type;
if (!isRenderObjectWidgetOrSubclass(classType)) {
return;
}

final methodsVisitor = _MethodsVisitor();
node.visitChildren(methodsVisitor);

final updateDeclaration = methodsVisitor.updateDeclaration;
final createDeclaration = methodsVisitor.createDeclaration;

if (createDeclaration == null) {
return;
}

final creationVisitor = _CreationVisitor();
createDeclaration.visitChildren(creationVisitor);

final createArgumentsLength =
_getCountableArgumentsLength(creationVisitor.arguments);
if (createArgumentsLength == 0) {
return;
}

if (updateDeclaration == null) {
if (node.abstractKeyword == null) {
_declarations.add(_DeclarationInfo(
node,
'Implementation for updateRenderObject method is absent.',
));
}

return;
}

final propertyAccessVisitor = _PropertyAccessVisitor();
updateDeclaration.visitChildren(propertyAccessVisitor);

if (createArgumentsLength != propertyAccessVisitor.propertyAccess.length) {
_declarations.add(_DeclarationInfo(
updateDeclaration,
"updateRenderObject method doesn't update all parameters, that are set in createRenderObject",
));
}
}

int _getCountableArgumentsLength(List<Expression> arguments) =>
arguments.where(
(argument) {
final expression =
argument is NamedExpression ? argument.expression : argument;

return expression is! NullLiteral &&
!isRenderObjectElementOrSubclass(expression.staticType);
},
).length;
}

class _MethodsVisitor extends GeneralizingAstVisitor<void> {
MethodDeclaration? createDeclaration;

MethodDeclaration? updateDeclaration;

@override
void visitMethodDeclaration(MethodDeclaration node) {
// ignore: deprecated_member_use
final name = node.name.name;
if (name == 'updateRenderObject') {
updateDeclaration = node;
} else if (name == 'createRenderObject') {
createDeclaration = node;
}
}
}

class _CreationVisitor extends RecursiveAstVisitor<void> {
final arguments = <Expression>[];

@override
void visitReturnStatement(ReturnStatement node) {
super.visitReturnStatement(node);

final expression = node.expression;

if (expression is InstanceCreationExpression) {
arguments.addAll(expression.argumentList.arguments);
}
}

@override
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
super.visitExpressionFunctionBody(node);

final expression = node.expression;

if (expression is InstanceCreationExpression) {
arguments.addAll(expression.argumentList.arguments);
}
}
}

class _PropertyAccessVisitor extends RecursiveAstVisitor<void> {
final propertyAccess = <Expression>[];

@override
void visitAssignmentExpression(AssignmentExpression node) {
super.visitAssignmentExpression(node);

final expression = node.leftHandSide;

if (expression is PropertyAccess) {
propertyAccess.add(expression);
} else if (expression is PrefixedIdentifier) {
propertyAccess.add(expression);
}
}
}

class _DeclarationInfo {
final Declaration node;
final String errorMessage;

const _DeclarationInfo(this.node, this.errorMessage);
}
18 changes: 18 additions & 0 deletions lib/src/utils/flutter_types_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ bool isWidgetOrSubclass(DartType? type) =>
bool isRenderObjectOrSubclass(DartType? type) =>
_isRenderObject(type) || _isSubclassOfRenderObject(type);

bool isRenderObjectWidgetOrSubclass(DartType? type) =>
_isRenderObjectWidget(type) || _isSubclassOfRenderObjectWidget(type);

bool isRenderObjectElementOrSubclass(DartType? type) =>
_isRenderObjectElement(type) || _isSubclassOfRenderObjectElement(type);

bool isWidgetStateOrSubclass(DartType? type) =>
_isWidgetState(type) || _isSubclassOfWidgetState(type);

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

bool _isSubclassOfRenderObject(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isRenderObject);

bool _isRenderObjectWidget(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'RenderObjectWidget';

bool _isSubclassOfRenderObjectWidget(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isRenderObjectWidget);

bool _isRenderObjectElement(DartType? type) =>
type?.getDisplayString(withNullability: false) == 'RenderObjectElement';

bool _isSubclassOfRenderObjectElement(DartType? type) =>
type is InterfaceType && type.allSupertypes.any(_isRenderObjectElement);
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart';
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/consistent_update_render_object/consistent_update_render_object_rule.dart';
import 'package:test/test.dart';

import '../../../../../helpers/rule_test_helper.dart';

const _correctExamplePath =
'consistent_update_render_object/examples/correct_example.dart';
const _incorrectExamplePath =
'consistent_update_render_object/examples/incorrect_example.dart';

void main() {
group('ConsistentUpdateRenderObjectRule', () {
test('initialization', () async {
final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath);
final issues = ConsistentUpdateRenderObjectRule().check(unit);

RuleTestHelper.verifyInitialization(
issues: issues,
ruleId: 'consistent-update-render-object',
severity: Severity.warning,
);
});

test('reports no issues', () async {
final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath);
final issues = ConsistentUpdateRenderObjectRule().check(unit);

RuleTestHelper.verifyNoIssues(issues);
});

test('reports about found issues', () async {
final unit = await RuleTestHelper.resolveFromFile(_incorrectExamplePath);
final issues = ConsistentUpdateRenderObjectRule().check(unit);

RuleTestHelper.verifyIssues(
issues: issues,
startLines: [42, 52, 98],
startColumns: [3, 1, 3],
locationTexts: [
'void updateRenderObject(BuildContext context, _RenderMenuItem renderObject) {}',
'class ColorFiltered extends SingleChildRenderObjectWidget {\n'
' const ColorFiltered({required this.value});\n'
'\n'
' final int value;\n'
'\n'
' @override\n'
' RenderObject createRenderObject(BuildContext context) =>\n'
' _ColorFilterRenderObject(colorFilter);\n'
'}',
'void updateRenderObject(\n'
' BuildContext context,\n'
' _RenderDecoration renderObject,\n'
' ) {\n'
' renderObject\n'
' ..expands = expands\n'
' ..textDirection = textDirection;\n'
' }',
],
messages: [
"updateRenderObject method doesn't update all parameters, that are set in createRenderObject",
'Implementation for updateRenderObject method is absent.',
"updateRenderObject method doesn't update all parameters, that are set in createRenderObject",
],
);
});
});
}
Loading