Skip to content
Merged
7 changes: 7 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:analysis_server_plugin/registry.dart';
import 'package:solid_lints/src/lints/avoid_debug_print_in_release/avoid_debug_print_in_release_rule.dart';
import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart';

/// The entry point for the Solid Lints analyser server plugin.
///
Expand All @@ -26,5 +27,11 @@ class SolidLintsPlugin extends Plugin {
registry.registerLintRule(
AvoidNonNullAssertionRule(),
);
registry.registerLintRule(
AvoidDebugPrintInReleaseRule(),
);
registry.registerLintRule(
ProperSuperCallsRule(),
);
}
}
156 changes: 35 additions & 121 deletions lib/src/lints/proper_super_calls/proper_super_calls_rule.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';
import 'package:analyzer/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/error/error.dart';
import 'package:solid_lints/src/lints/proper_super_calls/visitors/proper_super_calls_visitor.dart';

/// Ensures that `super` calls are made in the correct order for the following
/// StatefulWidget methods:
Expand Down Expand Up @@ -43,130 +43,44 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// super.dispose(); // OK
/// }
/// ```
class ProperSuperCallsRule extends SolidLintRule {
/// This lint rule represents
/// the error whether the initState and dispose methods
/// are called in the incorrect order
static const lintName = 'proper_super_calls';
static const _initState = 'initState';
static const _dispose = 'dispose';
static const _override = 'override';

/// This lint rule represents
/// the error whether super.initState() should be called first
static const _superInitStateCode = LintCode(
name: lintName,
problemMessage: "super.initState() should be first",
class ProperSuperCallsRule extends AnalysisRule {
/// Lint rule name.
static const String lintName = 'proper_super_calls';

/// Error code for invalid `super.initState()` placement.
static const LintCode superInitStateCode = LintCode(
lintName,
'super.initState() should be first',
);

/// This lint rule represents
/// the error whether super.dispose() should be called last
static const _superDisposeCode = LintCode(
name: lintName,
problemMessage: "super.dispose() should be last",
/// Error code for invalid `super.dispose()` placement.
static const LintCode superDisposeCode = LintCode(
lintName,
'super.dispose() should be last',
);

ProperSuperCallsRule._(super.config);

/// Creates a new instance of [ProperSuperCallsRule]
/// based on the lint configuration.
factory ProperSuperCallsRule.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
name: lintName,
configs: configs,
problemMessage: (_) => 'Proper super calls issue',
);
/// Creates an instance of [ProperSuperCallsRule].
ProperSuperCallsRule()
: super(
name: lintName,
description:
'Ensures proper ordering of Flutter lifecycle super calls.',
);

return ProperSuperCallsRule._(rule);
}
@override
LintCode get diagnosticCode => superInitStateCode;

@override
void run(
CustomLintResolver resolver,
DiagnosticReporter reporter,
CustomLintContext context,
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
context.registry.addMethodDeclaration(
(node) {
final methodName = node.name.toString();
final body = node.body;

if (methodName case _initState || _dispose
when body is BlockFunctionBody) {
final statements = body.block.statements;

_checkSuperCalls(
node,
methodName,
statements,
reporter,
);
}
},
registry.addMethodDeclaration(
this,
ProperSuperCallsVisitor(
rule: this,
context: context,
),
);
}

/// This method report an error whether `super.initState()`
/// or `super.dispose()` are called incorrect
void _checkSuperCalls(
MethodDeclaration node,
String methodName,
List<Statement> statements,
DiagnosticReporter reporter,
) {
final hasOverrideAnnotation =
node.metadata.any((annotation) => annotation.name.name == _override);

if (!hasOverrideAnnotation) return;
if (methodName == _initState && !_isSuperInitStateCalledFirst(statements)) {
reporter.atNode(
node,
_superInitStateCode,
);
}
if (methodName == _dispose && !_isSuperDisposeCalledLast(statements)) {
reporter.atNode(
node,
_superDisposeCode,
);
}
}

/// Returns `true` if `super.initState()` is called before other code in the
/// `initState` method, `false` otherwise.
bool _isSuperInitStateCalledFirst(List<Statement> statements) {
if (statements.isEmpty) return false;
final firstStatement = statements.first;

if (firstStatement is ExpressionStatement) {
final expression = firstStatement.expression;

final isSuperInitStateCalledFirst = expression is MethodInvocation &&
expression.target is SuperExpression &&
expression.methodName.toString() == _initState;

return isSuperInitStateCalledFirst;
}

return false;
}

/// Returns `true` if `super.dispose()` is called at the end of the `dispose`
/// method, `false` otherwise.
bool _isSuperDisposeCalledLast(List<Statement> statements) {
if (statements.isEmpty) return false;
final lastStatement = statements.last;

if (lastStatement is ExpressionStatement) {
final expression = lastStatement.expression;

final lastStatementIsSuperDispose = expression is MethodInvocation &&
expression.target is SuperExpression &&
expression.methodName.toString() == _dispose;

return lastStatementIsSuperDispose;
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart';

/// Visitor for [ProperSuperCallsRule].
class ProperSuperCallsVisitor extends SimpleAstVisitor<void> {
/// The rule associated with this visitor.
final ProperSuperCallsRule rule;

/// The context associated with this visitor.
final RuleContext context;

static const _initState = 'initState';
static const _dispose = 'dispose';
static const _flutterStateClass = 'State';

/// Creates a new instance of [ProperSuperCallsVisitor].
ProperSuperCallsVisitor({
required this.rule,
required this.context,
});

@override
void visitMethodDeclaration(MethodDeclaration node) {
final methodName = node.name.lexeme;
final body = node.body;

if ((methodName != _initState && methodName != _dispose) ||
body is! BlockFunctionBody) {
return;
}

if (!_overridesFlutterStateMethod(node)) {
return;
}

final statements = body.block.statements;
final reporter = context.currentUnit?.diagnosticReporter;

if (reporter == null) return;

if (methodName == _initState &&
!_isSuperCallFirst(statements, _initState)) {
reporter.atToken(
node.name,
ProperSuperCallsRule.superInitStateCode,
);
}

if (methodName == _dispose && !_isSuperCallLast(statements, _dispose)) {
reporter.atToken(
node.name,
ProperSuperCallsRule.superDisposeCode,
);
}
}

bool _overridesFlutterStateMethod(MethodDeclaration node) {
final classElement = node.declaredFragment?.element.enclosingElement;

if (classElement is! ClassElement) {
return false;
}

final methodName = node.name.lexeme;

final supertype = classElement.supertype;

if (supertype == null) {
return false;
}

final isStateSubclass = _isStateSubclass(supertype);

if (!isStateSubclass) {
return false;
}

return methodName == _initState || methodName == _dispose;
}

bool _isStateSubclass(InterfaceType supertype) {
final isStateSubclass = supertype.element.name == _flutterStateClass ||
supertype.allSupertypes.any(
(t) => t.element.name == _flutterStateClass,
);
return isStateSubclass;
}

bool _isSuperCallFirst(List<Statement> statements, String name) {
return statements.isNotEmpty && _isTargetSuperCall(statements.first, name);
}

bool _isSuperCallLast(List<Statement> statements, String name) {
return statements.isNotEmpty && _isTargetSuperCall(statements.last, name);
}

bool _isTargetSuperCall(Statement statement, String name) {
if (statement is! ExpressionStatement) {
return false;
}

final expression = statement.expression;

return expression is MethodInvocation &&
expression.target is SuperExpression &&
expression.methodName.name == name;
}
}
82 changes: 0 additions & 82 deletions lint_test/proper_super_calls_test.dart

This file was deleted.

1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ dev_dependencies:
args: ^2.6.0
analyzer_testing: ^0.1.9
test_reflective_loader: ^0.3.0

Loading
Loading