Skip to content

Commit

Permalink
Fix using original DCM code
Browse files Browse the repository at this point in the history
  • Loading branch information
solid-yuriiprykhodko committed Nov 23, 2023
1 parent 24e65ea commit f3d67ec
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,44 +27,48 @@ import 'package:analyzer/dart/ast/visitor.dart';
/// AST Visitor which finds all setState invocations and checks if they are
/// necessary
class AvoidUnnecessarySetStateMethodVisitor extends RecursiveAstVisitor<void> {
final Set<String> _classMethodsCallingSetState;
final Set<FunctionBody> _bodies;
final Set<String> _classMethodsNames;
final Set<FunctionBody> _classMethodBodies;

final _setStateInvocations = <MethodInvocation>[];
final _classMethodsInvocations = <MethodInvocation>[];

/// All setState invocations
/// Invocations of setState within visited methods.
Iterable<MethodInvocation> get setStateInvocations => _setStateInvocations;

/// Constructor for AvoidUnnecessarySetStateMethodVisitor
/// Invocations of methods within visited methods.
Iterable<MethodInvocation> get classMethodsInvocations =>
_classMethodsInvocations;

///
AvoidUnnecessarySetStateMethodVisitor(
this._classMethodsCallingSetState,
this._bodies,
this._classMethodsNames,
this._classMethodBodies,
);

@override
void visitMethodInvocation(MethodInvocation node) {
super.visitMethodInvocation(node);

final name = node.methodName.name;
final notInBody = _isNotInFunctionBody(node);
final isNotInCallback = _isNotInCallback(node);

if (name == 'setState' && notInBody) {
if (name == 'setState' && isNotInCallback) {
_setStateInvocations.add(node);
return;
}

final isClassMethodCallingSetState =
_classMethodsCallingSetState.contains(name);
final isClassMethod = _classMethodsNames.contains(name);
final isReturnValueUsed = node.realTarget != null;

if (isClassMethodCallingSetState && notInBody && !isReturnValueUsed) {
_setStateInvocations.add(node);
if (isClassMethod && isNotInCallback && !isReturnValueUsed) {
_classMethodsInvocations.add(node);
}
}

bool _isNotInFunctionBody(MethodInvocation node) =>
bool _isNotInCallback(MethodInvocation node) =>
node.thisOrAncestorMatching(
(parent) => parent is FunctionBody && !_bodies.contains(parent),
(parent) =>
parent is FunctionBody && !_classMethodBodies.contains(parent),
) ==
null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:collection/collection.dart';
import 'package:solid_lints/lints/avoid_unnecessary_setstate/visitor/avoid_unnecessary_set_state_method_visitor.dart';
import 'package:solid_lints/utils/types_utils.dart';

Expand All @@ -37,7 +38,7 @@ class AvoidUnnecessarySetStateVisitor extends RecursiveAstVisitor<void> {

final _setStateInvocations = <MethodInvocation>[];

/// All setState invocations in checkedMethods
/// Unnecessary setState invocations
Iterable<MethodInvocation> get setStateInvocations => _setStateInvocations;

@override
Expand All @@ -49,48 +50,69 @@ class AvoidUnnecessarySetStateVisitor extends RecursiveAstVisitor<void> {
return;
}

final declarations = node.members.whereType<MethodDeclaration>();
final classMethodsCallingSetState = declarations
.where(_hasSetStateInvocation)
// allow asynchronous triggers
.where((d) => !d.body.isAsynchronous)
.map((declaration) => declaration.name.lexeme)
.toSet();
final bodies = declarations.map((declaration) => declaration.body).toSet();
final methods = declarations
.where((member) => _checkedMethods.contains(member.name.lexeme))
.toList();

for (final method in methods) {
final methods = node.members.whereType<MethodDeclaration>();
final classMethodsNames =
methods.map((declaration) => declaration.name.lexeme).toSet();
final methodBodies = methods.map((declaration) => declaration.body).toSet();

final checkedMethods = methods.where(_isMethodChecked);
final uncheckedMethods = methods.whereNot(_isMethodChecked);

// memo for visited methods; prevents checking
final methodToHasSetState = <String, bool>{};

for (final method in checkedMethods) {
final visitor = AvoidUnnecessarySetStateMethodVisitor(
classMethodsCallingSetState,
bodies,
classMethodsNames,
methodBodies,
);
method.visitChildren(visitor);

_setStateInvocations.addAll([
...visitor.setStateInvocations,
]);
_setStateInvocations.addAll(visitor.setStateInvocations);
_setStateInvocations.addAll(
visitor.classMethodsInvocations
.where(
(invocation) => _containsSetState(
methodToHasSetState,
classMethodsNames,
methodBodies,
uncheckedMethods.firstWhere(
(method) => method.name.lexeme == invocation.methodName.name,
),
),
)
.toList(),
);
}
}

bool _hasSetStateInvocation(MethodDeclaration node) {
final visitor = _HasSetStateMethodVisitor();
node.visitChildren(visitor);
return visitor.hasSetStateCalls;
}
}

class _HasSetStateMethodVisitor extends RecursiveAstVisitor<void> {
bool _hasSetStateCalls = false;
bool _isMethodChecked(MethodDeclaration m) =>
_checkedMethods.contains(m.name.lexeme);

bool get hasSetStateCalls => _hasSetStateCalls;
bool _containsSetState(
Map<String, bool> methodToHasSetState,
Set<String> classMethodsNames,
Set<FunctionBody> bodies,
MethodDeclaration declaration,
) {
final type = declaration.returnType?.type;
if (type != null && (type.isDartAsyncFuture || type.isDartAsyncFutureOr)) {
return false;
}

@override
void visitMethodInvocation(MethodInvocation node) {
if (node.methodName.name == 'setState') {
_hasSetStateCalls = true;
final name = declaration.name.lexeme;
if (methodToHasSetState.containsKey(name) && methodToHasSetState[name]!) {
return true;
}
super.visitMethodInvocation(node);

final visitor =
AvoidUnnecessarySetStateMethodVisitor(classMethodsNames, bodies);
declaration.visitChildren(visitor);

final hasSetState = visitor.setStateInvocations.isNotEmpty;

methodToHasSetState[name] = hasSetState;

return hasSetState;
}
}

0 comments on commit f3d67ec

Please sign in to comment.