From f31c2f1236694eddf82e81f2beb3dcab925358b3 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Wed, 3 Jun 2026 14:08:10 +0300 Subject: [PATCH 01/16] Updated avoid_unnecessary_set_state rule and its visitor to use AnalysisRule. --- .../avoid_unnecessary_set_state_rule.dart | 61 +++++++++---------- .../avoid_unnecessary_set_state_visitor.dart | 16 +++-- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart b/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart index 0773e580..0d6d4323 100644 --- a/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart +++ b/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart @@ -1,8 +1,8 @@ -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.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/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; -import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// A rule which warns when setState is called inside initState, didUpdateWidget /// or build methods and when it's called from a sync method that is called @@ -55,37 +55,36 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// if (mounted) setState(() => foo = 'bar'); /// } /// ``` -class AvoidUnnecessarySetStateRule extends SolidLintRule { - /// The lint name of this lint rule that represents - /// the error whether we use setState in inappropriate way. - static const lintName = 'avoid_unnecessary_setstate'; +class AvoidUnnecessarySetStateRule extends AnalysisRule { + /// The name of the lint rule. + static const lintName = 'avoid_unnecessary_set_state'; - AvoidUnnecessarySetStateRule._(super.config); + /// The message shown when the lint rule is triggered. + static const lintMessage = 'Avoid calling unnecessary setState. ' + 'Consider changing the state directly.'; - /// Creates a new instance of [AvoidUnnecessarySetStateRule] - /// based on the lint configuration. - factory AvoidUnnecessarySetStateRule.createRule(CustomLintConfigs configs) { - final rule = RuleConfig( - name: lintName, - configs: configs, - problemMessage: (_) => 'Avoid calling unnecessary setState. ' - 'Consider changing the state directly.', - ); - return AvoidUnnecessarySetStateRule._(rule); - } + /// The lint code for this rule. + static const LintCode _code = LintCode( + lintName, + lintMessage, + ); + + /// Creates a new instance of [AvoidUnnecessarySetStateRule]. + AvoidUnnecessarySetStateRule() + : super( + name: lintName, + description: lintMessage, + ); + + @override + LintCode get diagnosticCode => _code; @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, ) { - context.registry.addClassDeclaration((node) { - final visitor = AvoidUnnecessarySetStateVisitor(); - visitor.visitClassDeclaration(node); - for (final element in visitor.setStateInvocations) { - reporter.atNode(element, code); - } - }); + final visitor = AvoidUnnecessarySetStateVisitor(this); + registry.addClassDeclaration(this, visitor); } } diff --git a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart index 5679d5e8..894d12c5 100644 --- a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart +++ b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart @@ -24,10 +24,11 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:collection/collection.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart'; import 'package:solid_lints/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_method_visitor.dart'; import 'package:solid_lints/src/utils/types_utils.dart'; -/// AST visitor which checks if class is State, in case yes checks its methods +/// Visitor for [AvoidUnnecessarySetStateRule]. class AvoidUnnecessarySetStateVisitor extends RecursiveAstVisitor { static const _checkedMethods = [ 'initState', @@ -36,10 +37,13 @@ class AvoidUnnecessarySetStateVisitor extends RecursiveAstVisitor { 'build', ]; - final _setStateInvocations = []; + /// The rule associated with this visitor. + final AvoidUnnecessarySetStateRule rule; + + /// Creates an instance of [AvoidUnnecessarySetStateVisitor]. + AvoidUnnecessarySetStateVisitor(this.rule); - /// Unnecessary setState invocations - Iterable get setStateInvocations => _setStateInvocations; + final _setStateInvocations = []; @override void visitClassDeclaration(ClassDeclaration node) { @@ -83,6 +87,10 @@ class AvoidUnnecessarySetStateVisitor extends RecursiveAstVisitor { ) .toList(), ); + + for (final element in visitor.setStateInvocations) { + rule.reportAtNode(element); + } } } From 0c6e51514c7956b942b723e5ffeed0e1d30b8551 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Wed, 3 Jun 2026 14:10:42 +0300 Subject: [PATCH 02/16] Fixed previous commit. --- .../visitors/avoid_unnecessary_set_state_visitor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart index 894d12c5..14b5d956 100644 --- a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart +++ b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart @@ -88,7 +88,7 @@ class AvoidUnnecessarySetStateVisitor extends RecursiveAstVisitor { .toList(), ); - for (final element in visitor.setStateInvocations) { + for (final element in _setStateInvocations) { rule.reportAtNode(element); } } From bd8a5dfeae95993c8c87050011d1e0dc3ef665f8 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Wed, 3 Jun 2026 15:03:18 +0300 Subject: [PATCH 03/16] Updated tests for avoid_unnecessary_set_state rule. --- ...avoid_unnecessary_set_state_rule_test.dart | 330 ++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart diff --git a/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart b/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart new file mode 100644 index 00000000..06a23bdb --- /dev/null +++ b/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart @@ -0,0 +1,330 @@ +// MIT License +// +// Copyright (c) 2020-2021 Dart Code Checker team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidUnnecessarySetStateRuleTest); + }); +} + +@reflectiveTest +class AvoidUnnecessarySetStateRuleTest extends AnalysisRuleTest { + @override + void setUp() { + final flutter = newPackage('flutter'); + + flutter.addFile( + 'lib/src/widgets/framework.dart', + r''' +abstract class Widget {} + +abstract class StatefulWidget extends Widget {} + +class BuildContext {} + +abstract class State { + void initState() {} + void didUpdateWidget(T oldWidget) {} + void setState(void Function() fn) {} + + Widget build(BuildContext context); +} + +class Text extends Widget { + final Object? data; + Text(this.data); +} + +class ElevatedButton extends Widget { + final Function()? onPressed; + final Function()? onLongPress; + final Widget? child; + + ElevatedButton({this.onPressed, this.onHover, this.onLongPress, this.child}); +} +''', + ); + + rule = AvoidUnnecessarySetStateRule(); + super.setUp(); + } + + @override + String get analysisRule => AvoidUnnecessarySetStateRule.lintName; + + void test_reports_set_state_in_init_state() async { + await assertDiagnostics( + r''' +import 'package:flutter/src/widgets/framework.dart'; + +class _MyWidgetState extends State { + String _myString = ''; + + @override + void initState() { + super.initState(); + + setState(() { + _myString = "Hello"; + }); + } + + @override + Widget build(BuildContext context) { + return ElevatedButton( + child: Text(_myString), + ); + } +} + ''', + [lint(194, 47)], + ); + } + + void test_reports_set_state_in_init_state_with_condition() async { + await assertDiagnostics( + r''' +import 'package:flutter/src/widgets/framework.dart'; + +class _MyWidgetState extends State { + String _myString = ''; + final bool _condition = true; + + @override + void initState() { + super.initState(); + + if (_condition) { + setState(() { + _myString = "Hello"; + }); + } + } + + @override + Widget build(BuildContext context) { + return ElevatedButton( + child: Text(_myString), + ); + } +} + ''', + [lint(250, 51)], + ); + } + + void test_reports_set_state_in_init_state_through_method() async { + await assertDiagnostics( + r''' +import 'package:flutter/src/widgets/framework.dart'; + +class _MyWidgetState extends State { + String _myString = ''; + final bool _condition = true; + + @override + void initState() { + super.initState(); + + myStateUpdateMethod(); + } + + void myStateUpdateMethod() { + setState(() { + _myString = "Hello"; + }); + } + + @override + Widget build(BuildContext context) { + return ElevatedButton( + child: Text(_myString), + ); + } +} + ''', + [lint(226, 21)], + ); + } + + void test_reports_set_state_in_did_update_widget() async { + await assertDiagnostics( + r''' +import 'package:flutter/src/widgets/framework.dart'; + +class _MyWidgetState extends State { + String _myString = ''; + + @override + void didUpdateWidget(StatefulWidget oldWidget) { + super.didUpdateWidget(oldWidget); + setState(() { + _myString = "Hello"; + }); + } + + @override + Widget build(BuildContext context) { + return ElevatedButton( + child: Text(_myString), + ); + } +} + ''', + [lint(238, 47)], + ); + } + + void test_reports_set_state_in_build_method() async { + await assertDiagnostics( + r''' +import 'package:flutter/src/widgets/framework.dart'; + +class _MyWidgetState extends State { + String _myString = ''; + + @override + Widget build(BuildContext context) { + setState(() { + _myString = "Hello"; + }); + + return ElevatedButton( + child: Text(_myString), + ); + } +} + ''', + [lint(188, 47)], + ); + } + + void test_reports_set_state_in_build_method_with_condition() async { + await assertDiagnostics( + r''' +import 'package:flutter/src/widgets/framework.dart'; + +class _MyWidgetState extends State { + String _myString = ''; + final bool _condition = true; + + @override + Widget build(BuildContext context) { + if (_condition) { + setState(() { + _myString = "Hello"; + }); + } + + return ElevatedButton( + child: Text(_myString), + ); + } +} + ''', + [lint(244, 51)], + ); + } + + void test_reports_set_state_in_build_method_through_method() async { + await assertDiagnostics( + r''' +import 'package:flutter/src/widgets/framework.dart'; + +class _MyWidgetState extends State { + String _myString = ''; + + void myStateUpdateMethod() { + setState(() { + _myString = "Hello"; + }); + } + + @override + Widget build(BuildContext context) { + myStateUpdateMethod(); + + return ElevatedButton( + child: Text(_myString), + ); + } +} + ''', + [lint(277, 21)], + ); + } + + void test_does_not_report_set_state_in_button_on_pressed() async { + await assertNoDiagnostics( + r''' +import 'package:flutter/src/widgets/framework.dart'; + +class _MyWidgetState extends State { + String _myString = ''; + + void myStateUpdateMethod() { + setState(() { + _myString = "Hello"; + }); + } + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: myStateUpdateMethod, + child: Text(_myString), + ); + } +} + ''', + ); + } + + void test_does_not_report_set_state_in_button_on_long_press() async { + await assertNoDiagnostics( + r''' +import 'package:flutter/src/widgets/framework.dart'; + +class _MyWidgetState extends State { + String _myString = ''; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onLongPress: () { + setState(() { + _myString = 'data'; + }); + }, + child: Text(_myString), + ); + } +} + ''', + ); + } +} From b46116d6f733a8de1227fc0eb21e4b42688481c1 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Wed, 3 Jun 2026 15:04:09 +0300 Subject: [PATCH 04/16] Delete previous test file for avoid_unnecessary_set_state rule --- .../avoid_unnecessary_setstate_test.dart | 108 ------------------ 1 file changed, 108 deletions(-) delete mode 100644 lint_test/avoid_unnecessary_setstate_test.dart diff --git a/lint_test/avoid_unnecessary_setstate_test.dart b/lint_test/avoid_unnecessary_setstate_test.dart deleted file mode 100644 index b308515b..00000000 --- a/lint_test/avoid_unnecessary_setstate_test.dart +++ /dev/null @@ -1,108 +0,0 @@ -// MIT License -// -// Copyright (c) 2020-2021 Dart Code Checker team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// ignore_for_file: member_ordering, prefer_match_file_name - -import 'package:flutter/material.dart'; - -/// Check unnecessary setstate fail -/// `avoid_unnecessary_setstate` -class MyWidget extends StatefulWidget { - @override - _MyWidgetState createState() => _MyWidgetState(); -} - -class _MyWidgetState extends State { - String _myString = ''; - final bool _condition = true; - - @override - void initState() { - super.initState(); - - methodWithoutSetState(); - - // expect_lint: avoid_unnecessary_setstate - setState(() { - _myString = "Hello"; - }); - - if (_condition) { - // expect_lint: avoid_unnecessary_setstate - setState(() { - _myString = "Hello"; - }); - } - - // expect_lint: avoid_unnecessary_setstate - myStateUpdateMethod(); - } - - @override - void didUpdateWidget(MyWidget oldWidget) { - super.didUpdateWidget(oldWidget); - // expect_lint: avoid_unnecessary_setstate - setState(() { - _myString = "Hello"; - }); - } - - void myStateUpdateMethod() { - setState(() { - _myString = "Hello"; - }); - } - - void methodWithoutSetState() { - _myString = 'hey'; - } - - @override - Widget build(BuildContext context) { - // expect_lint: avoid_unnecessary_setstate - setState(() { - _myString = "Hello"; - }); - - if (_condition) { - // expect_lint: avoid_unnecessary_setstate - setState(() { - _myString = "Hello"; - }); - } - - // expect_lint: avoid_unnecessary_setstate - myStateUpdateMethod(); - - return ElevatedButton( - onPressed: myStateUpdateMethod, - onHover: (_) => methodWithoutSetState(), - onLongPress: () { - setState(() { - _myString = 'data'; - }); - }, - child: Text(_myString), - ); - } -} From 769eabc504b1f11aa64c488b4f80752f23e867d8 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Wed, 3 Jun 2026 15:22:59 +0300 Subject: [PATCH 05/16] Updated the avoid_unnecessary_set_state visitor from recursive to simple. --- .../visitors/avoid_unnecessary_set_state_visitor.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart index 14b5d956..c3ef47a0 100644 --- a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart +++ b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart @@ -29,7 +29,7 @@ import 'package:solid_lints/src/lints/avoid_unnecessary_setstate/visitors/avoid_ import 'package:solid_lints/src/utils/types_utils.dart'; /// Visitor for [AvoidUnnecessarySetStateRule]. -class AvoidUnnecessarySetStateVisitor extends RecursiveAstVisitor { +class AvoidUnnecessarySetStateVisitor extends SimpleAstVisitor { static const _checkedMethods = [ 'initState', 'didUpdateWidget', @@ -54,7 +54,13 @@ class AvoidUnnecessarySetStateVisitor extends RecursiveAstVisitor { return; } - final methods = node.members.whereType(); + final body = node.body; + + if (body is! BlockClassBody) { + return; + } + + final methods = body.members.whereType(); final classMethodsNames = methods.map((declaration) => declaration.name.lexeme).toSet(); final methodBodies = methods.map((declaration) => declaration.body).toSet(); From f00cfa216996fbafb9de6a6d9408391b2e4f8ef9 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Wed, 3 Jun 2026 15:25:04 +0300 Subject: [PATCH 06/16] Removed unnecessary set up from avoid_unnecessary_set_state rule tests --- .../avoid_unnecessary_set_state_rule_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart b/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart index 06a23bdb..22518e5b 100644 --- a/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart +++ b/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart @@ -64,7 +64,7 @@ class ElevatedButton extends Widget { final Function()? onLongPress; final Widget? child; - ElevatedButton({this.onPressed, this.onHover, this.onLongPress, this.child}); + ElevatedButton({this.onPressed, this.onLongPress, this.child}); } ''', ); From fc022971b5b077572e0055e4b56fb44258a4a0a0 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 09:19:49 +0300 Subject: [PATCH 07/16] Update lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart Co-authored-by: danylo-safonov-solid <116712879+danylo-safonov-solid@users.noreply.github.com> --- .../avoid_unnecessary_set_state_rule.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart b/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart index 0d6d4323..9daf88b4 100644 --- a/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart +++ b/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart @@ -64,7 +64,7 @@ class AvoidUnnecessarySetStateRule extends AnalysisRule { 'Consider changing the state directly.'; /// The lint code for this rule. - static const LintCode _code = LintCode( + static const _code = LintCode( lintName, lintMessage, ); From bdbc898c86ba0562e7b36a56b67e37af933a7f30 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 09:29:40 +0300 Subject: [PATCH 08/16] Address PR suggestions --- .../avoid_unnecessary_set_state_rule.dart | 12 ++++++------ .../avoid_unnecessary_set_state_visitor.dart | 4 ++-- .../avoid_unnecessary_set_state_rule_test.dart | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart b/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart index 9daf88b4..afdf2fdf 100644 --- a/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart +++ b/lib/src/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart @@ -57,23 +57,23 @@ import 'package:solid_lints/src/lints/avoid_unnecessary_setstate/visitors/avoid_ /// ``` class AvoidUnnecessarySetStateRule extends AnalysisRule { /// The name of the lint rule. - static const lintName = 'avoid_unnecessary_set_state'; + static const _lintName = 'avoid_unnecessary_setstate'; /// The message shown when the lint rule is triggered. - static const lintMessage = 'Avoid calling unnecessary setState. ' + static const _lintMessage = 'Avoid calling unnecessary setState. ' 'Consider changing the state directly.'; /// The lint code for this rule. static const _code = LintCode( - lintName, - lintMessage, + _lintName, + _lintMessage, ); /// Creates a new instance of [AvoidUnnecessarySetStateRule]. AvoidUnnecessarySetStateRule() : super( - name: lintName, - description: lintMessage, + name: _lintName, + description: _lintMessage, ); @override diff --git a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart index c3ef47a0..9ab99305 100644 --- a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart +++ b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart @@ -40,11 +40,11 @@ class AvoidUnnecessarySetStateVisitor extends SimpleAstVisitor { /// The rule associated with this visitor. final AvoidUnnecessarySetStateRule rule; + final _setStateInvocations = []; + /// Creates an instance of [AvoidUnnecessarySetStateVisitor]. AvoidUnnecessarySetStateVisitor(this.rule); - final _setStateInvocations = []; - @override void visitClassDeclaration(ClassDeclaration node) { super.visitClassDeclaration(node); diff --git a/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart b/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart index 22518e5b..0e160ae0 100644 --- a/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart +++ b/test/lints/avoid_unnecessary_set_state/avoid_unnecessary_set_state_rule_test.dart @@ -74,7 +74,7 @@ class ElevatedButton extends Widget { } @override - String get analysisRule => AvoidUnnecessarySetStateRule.lintName; + String get analysisRule => rule.name; void test_reports_set_state_in_init_state() async { await assertDiagnostics( From 5fe1849325506d05139d7dbb137b19fd231b6cf8 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 10:08:16 +0300 Subject: [PATCH 09/16] Updated sdk requirements --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f85b1309..70168359 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,7 +8,7 @@ documentation: https://solid-software.github.io/solid_lints/docs/intro topics: [lints, linter, lint, analysis, analyzer] environment: - sdk: ">=3.5.0 <4.0.0" + sdk: ">=3.9.0 <4.0.0" dependencies: analyzer: ^10.0.1 From a7a9febea467078dd18c094ce3b5bb8078e7ce1d Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 11:56:28 +0300 Subject: [PATCH 10/16] Updated rule to private in visitor --- .../avoid_unnecessary_set_state_visitor.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart index 9ab99305..4b47d939 100644 --- a/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart +++ b/lib/src/lints/avoid_unnecessary_setstate/visitors/avoid_unnecessary_set_state_visitor.dart @@ -38,12 +38,12 @@ class AvoidUnnecessarySetStateVisitor extends SimpleAstVisitor { ]; /// The rule associated with this visitor. - final AvoidUnnecessarySetStateRule rule; + final AvoidUnnecessarySetStateRule _rule; final _setStateInvocations = []; /// Creates an instance of [AvoidUnnecessarySetStateVisitor]. - AvoidUnnecessarySetStateVisitor(this.rule); + AvoidUnnecessarySetStateVisitor(this._rule); @override void visitClassDeclaration(ClassDeclaration node) { @@ -61,8 +61,9 @@ class AvoidUnnecessarySetStateVisitor extends SimpleAstVisitor { } final methods = body.members.whereType(); - final classMethodsNames = - methods.map((declaration) => declaration.name.lexeme).toSet(); + final classMethodsNames = methods + .map((declaration) => declaration.name.lexeme) + .toSet(); final methodBodies = methods.map((declaration) => declaration.body).toSet(); final checkedMethods = methods.where(_isMethodChecked); @@ -95,7 +96,7 @@ class AvoidUnnecessarySetStateVisitor extends SimpleAstVisitor { ); for (final element in _setStateInvocations) { - rule.reportAtNode(element); + _rule.reportAtNode(element); } } } @@ -119,8 +120,10 @@ class AvoidUnnecessarySetStateVisitor extends SimpleAstVisitor { return true; } - final visitor = - AvoidUnnecessarySetStateMethodVisitor(classMethodsNames, bodies); + final visitor = AvoidUnnecessarySetStateMethodVisitor( + classMethodsNames, + bodies, + ); declaration.visitChildren(visitor); final hasSetState = visitor.setStateInvocations.isNotEmpty; From 1fda18bab8d20632d61d845cb31b4ece73d8bc6e Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 12:49:34 +0300 Subject: [PATCH 11/16] Updated newline before return rule and its visitor --- .../newline_before_return_rule.dart | 75 +++++++++++-------- .../newline_before_return_visitor.dart | 53 +++++++------ 2 files changed, 75 insertions(+), 53 deletions(-) diff --git a/lib/src/lints/newline_before_return/newline_before_return_rule.dart b/lib/src/lints/newline_before_return/newline_before_return_rule.dart index 3624a7c6..e2483431 100644 --- a/lib/src/lints/newline_before_return/newline_before_return_rule.dart +++ b/lib/src/lints/newline_before_return/newline_before_return_rule.dart @@ -21,11 +21,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.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/newline_before_return/visitors/newline_before_return_visitor.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; -import 'package:solid_lints/src/models/solid_lint_rule.dart'; // Inspired by ESLint (https://eslint.org/docs/rules/newline-before-return) @@ -72,38 +72,51 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// } /// } /// ``` -class NewlineBeforeReturnRule extends SolidLintRule { - /// This lint rule represents the error if - /// newline is missing before return statement - static const String lintName = 'newline_before_return'; +class NewlineBeforeReturnRule extends AnalysisRule { + /// The name of the lint rule. + static const String _lintName = 'newline_before_return'; - NewlineBeforeReturnRule._(super.config); + /// The message shown when the lint is triggered. + static const String _lintMessage = 'Missing blank line before return.'; - /// Creates a new instance of [NewlineBeforeReturnRule] - /// based on the lint configuration. - factory NewlineBeforeReturnRule.createRule(CustomLintConfigs configs) { - final rule = RuleConfig( - configs: configs, - name: lintName, - problemMessage: (value) => "Missing blank line before return.", - ); + /// Lint code for this rule. + static const LintCode _code = LintCode( + _lintName, + _lintMessage, + ); - return NewlineBeforeReturnRule._(rule); - } + /// Creates a new instance of [NewlineBeforeReturnRule]. + NewlineBeforeReturnRule() + : super( + name: _lintName, + description: _lintMessage, + ); @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, - ) { - context.registry.addReturnStatement((node) { - final visitor = NewLineBeforeReturnVisitor(resolver.lineInfo); - visitor.visitReturnStatement(node); + LintCode get diagnosticCode => _code; - for (final element in visitor.statements) { - reporter.atNode(element, code); - } - }); + @override + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, + ) { + final visitor = NewLineBeforeReturnVisitor(this); + registry.addReturnStatement(this, visitor); } + + // @override + // void run( + // CustomLintResolver resolver, + // DiagnosticReporter reporter, + // CustomLintContext context, + // ) { + // context.registry.addReturnStatement((node) { + // final visitor = NewLineBeforeReturnVisitor(resolver.lineInfo); + // visitor.visitReturnStatement(node); + + // for (final element in visitor.statements) { + // reporter.atNode(element, code); + // } + // }); + // } } diff --git a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart index cad8b50a..04f7fa15 100644 --- a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart +++ b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart @@ -24,18 +24,14 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/visitor.dart'; -import 'package:analyzer/source/line_info.dart'; +import 'package:solid_lints/src/lints/newline_before_return/newline_before_return_rule.dart'; -/// The AST visitor that will all return statements. +/// Visitor for [NewlineBeforeReturnRule]. class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { - final LineInfo _lineInfo; - final _statements = []; + final NewlineBeforeReturnRule _rule; /// Creates instance of [NewLineBeforeReturnVisitor] with line info - NewLineBeforeReturnVisitor(this._lineInfo); - - /// List of all return statements - Iterable get statements => _statements; + NewLineBeforeReturnVisitor(this._rule); @override void visitReturnStatement(ReturnStatement node) { @@ -43,9 +39,9 @@ class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { if (!_statementIsInBlock(node)) return; if (_statementIsFirstInBlock(node)) return; - if (_statementHasNewLineBefore(node, _lineInfo)) return; + if (_statementHasNewLineBefore(node)) return; - _statements.add(node); + _rule.reportAtNode(node); } static bool _statementIsInBlock(ReturnStatement node) => node.parent is Block; @@ -55,26 +51,27 @@ class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { static bool _statementHasNewLineBefore( ReturnStatement node, - LineInfo lineInfo, ) { - final previousTokenLineNumber = - lineInfo.getLocation(node.returnKeyword.previous!.end).lineNumber; + final source = node.root.toSource(); - final lastNotEmptyLineToken = _optimalToken(node.returnKeyword, lineInfo); - final tokenLineNumber = - lineInfo.getLocation(lastNotEmptyLineToken.offset).lineNumber; + final previousToken = node.returnKeyword.previous!; + final lastNotEmptyLineToken = _optimalToken(node.returnKeyword, source); - return tokenLineNumber > previousTokenLineNumber + 1; + return _hasBlankLineBetween( + previousToken, + lastNotEmptyLineToken, + source, + ); } /// If return statement has comment above ignores all the comment lines - static Token _optimalToken(Token token, LineInfo lineInfo) { + static Token _optimalToken(Token token, String source) { var optimalToken = token; var commentToken = _latestCommentToken(token); + while (commentToken != null && - lineInfo.getLocation(commentToken.end).lineNumber + 1 >= - lineInfo.getLocation(optimalToken.offset).lineNumber) { + !_hasBlankLineBetween(commentToken, optimalToken, source)) { optimalToken = commentToken; commentToken = commentToken.previous; } @@ -84,10 +81,22 @@ class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { static Token? _latestCommentToken(Token token) { Token? latestCommentToken = token.precedingComments; - while (latestCommentToken?.next != null) { - latestCommentToken = latestCommentToken?.next; + + if (latestCommentToken == null) return null; + + while (latestCommentToken!.next != null) { + latestCommentToken = latestCommentToken.next; } return latestCommentToken; } + + static bool _hasBlankLineBetween(Token a, Token b, String source) { + final aEnd = a.end; + final bStart = b.offset; + + final between = source.substring(aEnd, bStart); + + return between.contains('\n\n'); + } } From 053198c96f4205dc72435e88e726a50aa77db2aa Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 13:53:39 +0300 Subject: [PATCH 12/16] Migrated tests for newline_before_return_rule --- .../newline_before_return_rule_test.dart | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 test/lints/newline_before_return/newline_before_return_rule_test.dart diff --git a/test/lints/newline_before_return/newline_before_return_rule_test.dart b/test/lints/newline_before_return/newline_before_return_rule_test.dart new file mode 100644 index 00000000..ffce4204 --- /dev/null +++ b/test/lints/newline_before_return/newline_before_return_rule_test.dart @@ -0,0 +1,146 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:solid_lints/src/lints/newline_before_return/newline_before_return_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(NewlineBeforeReturnRuleTest); + }); +} + +@reflectiveTest +class NewlineBeforeReturnRuleTest extends AnalysisRuleTest { + @override + void setUp() { + rule = NewlineBeforeReturnRule(); + super.setUp(); + } + + @override + String get analysisRule => rule.name; + + void test_reports_no_newline_before_return_value() async { + await assertDiagnostics( + r''' +int method() { + final a = 0; + return 1; +} + ''', + [lint(32, 9)], + ); + } + + void test_reports_no_newline_before_return() async { + await assertDiagnostics( + r''' +void method() { + final a = 0; + return; +} + ''', + [lint(33, 7)], + ); + } + + void test_reports_no_newline_before_return_with_comment() async { + await assertDiagnostics( + r''' +void method() { + final a = 0; + // Comment + return; +} + ''', + [lint(47, 7)], + ); + } + + void test_does_not_report_no_newline_before_single_statement_return() async { + await assertNoDiagnostics(r''' +void method() { + return; +} + '''); + } + + void test_does_not_report_newline_before__return() async { + await assertNoDiagnostics(r''' +void method() { + final a = 0; + + return; +} + '''); + } + + void + test_does_not_report_no_newline_before_single_statement_return_value() async { + await assertNoDiagnostics(r''' +int method() { + return 1; +} + '''); + } + + void + test_does_not_report_no_newline_before_single_statement_nested_return() async { + await assertNoDiagnostics(r''' +class Foo{ + void bar(void Function()) { + return; + } +} + +void fun() { + final foo = Foo(); + foo.bar(() { + return; + }); +} + '''); + } + + void test_reports_no_newline_before_return_nested() async { + await assertDiagnostics( + r''' +class Foo{ + void bar(void Function()) { + return; + } +} + +void fun() { + final foo = Foo(); + foo.bar(() { + final a = 1; + return; + }); +} + ''', + [lint(130, 7)], + ); + } + + void test_reports_no_newline_before_two_return_nested() async { + await assertDiagnostics( + r''' +class Foo{ + void bar(void Function()) { + return; + } +} + +void fun() { + final foo = Foo(); + foo.bar(() { + final a = 1; + return; + }); + return; +} + ''', + [lint(130, 7), lint(146, 7)], + ); + } +} From fee68eff4225cba3586dc3a3cd75a8caa100cfc5 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 14:17:17 +0300 Subject: [PATCH 13/16] Fixed rule and some tests --- .../newline_before_return_rule.dart | 18 +----------------- .../newline_before_return_visitor.dart | 18 +++++++++++++----- .../newline_before_return_rule_test.dart | 4 ++-- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/lib/src/lints/newline_before_return/newline_before_return_rule.dart b/lib/src/lints/newline_before_return/newline_before_return_rule.dart index e2483431..ce261cd0 100644 --- a/lib/src/lints/newline_before_return/newline_before_return_rule.dart +++ b/lib/src/lints/newline_before_return/newline_before_return_rule.dart @@ -100,23 +100,7 @@ class NewlineBeforeReturnRule extends AnalysisRule { RuleVisitorRegistry registry, RuleContext context, ) { - final visitor = NewLineBeforeReturnVisitor(this); + final visitor = NewLineBeforeReturnVisitor(this, context); registry.addReturnStatement(this, visitor); } - - // @override - // void run( - // CustomLintResolver resolver, - // DiagnosticReporter reporter, - // CustomLintContext context, - // ) { - // context.registry.addReturnStatement((node) { - // final visitor = NewLineBeforeReturnVisitor(resolver.lineInfo); - // visitor.visitReturnStatement(node); - - // for (final element in visitor.statements) { - // reporter.atNode(element, code); - // } - // }); - // } } diff --git a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart index 04f7fa15..6c5c7ba6 100644 --- a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart +++ b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart @@ -21,6 +21,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import 'package:analyzer/analysis_rule/rule_context.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/visitor.dart'; @@ -30,16 +31,20 @@ import 'package:solid_lints/src/lints/newline_before_return/newline_before_retur class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { final NewlineBeforeReturnRule _rule; + final RuleContext _context; + /// Creates instance of [NewLineBeforeReturnVisitor] with line info - NewLineBeforeReturnVisitor(this._rule); + NewLineBeforeReturnVisitor(this._rule, this._context); @override void visitReturnStatement(ReturnStatement node) { super.visitReturnStatement(node); + final source = _context.allUnits.first.content; + if (!_statementIsInBlock(node)) return; if (_statementIsFirstInBlock(node)) return; - if (_statementHasNewLineBefore(node)) return; + if (_statementHasNewLineBefore(node, source)) return; _rule.reportAtNode(node); } @@ -51,10 +56,10 @@ class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { static bool _statementHasNewLineBefore( ReturnStatement node, + String source, ) { - final source = node.root.toSource(); - final previousToken = node.returnKeyword.previous!; + final lastNotEmptyLineToken = _optimalToken(node.returnKeyword, source); return _hasBlankLineBetween( @@ -95,8 +100,11 @@ class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { final aEnd = a.end; final bStart = b.offset; + if (aEnd > bStart) return false; + final between = source.substring(aEnd, bStart); + final hasBlankLine = between.contains(RegExp(r'\n[ \t]*\r?\n')); - return between.contains('\n\n'); + return hasBlankLine; } } diff --git a/test/lints/newline_before_return/newline_before_return_rule_test.dart b/test/lints/newline_before_return/newline_before_return_rule_test.dart index ffce4204..34ee9760 100644 --- a/test/lints/newline_before_return/newline_before_return_rule_test.dart +++ b/test/lints/newline_before_return/newline_before_return_rule_test.dart @@ -52,7 +52,7 @@ void method() { return; } ''', - [lint(47, 7)], + [lint(46, 7)], ); } @@ -64,7 +64,7 @@ void method() { '''); } - void test_does_not_report_newline_before__return() async { + void test_does_not_report_newline_before_return() async { await assertNoDiagnostics(r''' void method() { final a = 0; From dfde80dd422288808d317cc1e03d741b4b2fdc2b Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 14:34:59 +0300 Subject: [PATCH 14/16] Optimize newline_before_return using LineInfo --- .../newline_before_return_rule.dart | 2 +- .../newline_before_return_visitor.dart | 55 +++++++------------ 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/lib/src/lints/newline_before_return/newline_before_return_rule.dart b/lib/src/lints/newline_before_return/newline_before_return_rule.dart index ce261cd0..68e8360e 100644 --- a/lib/src/lints/newline_before_return/newline_before_return_rule.dart +++ b/lib/src/lints/newline_before_return/newline_before_return_rule.dart @@ -100,7 +100,7 @@ class NewlineBeforeReturnRule extends AnalysisRule { RuleVisitorRegistry registry, RuleContext context, ) { - final visitor = NewLineBeforeReturnVisitor(this, context); + final visitor = NewLineBeforeReturnVisitor(this); registry.addReturnStatement(this, visitor); } } diff --git a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart index 6c5c7ba6..7e1d6189 100644 --- a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart +++ b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart @@ -21,30 +21,26 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import 'package:analyzer/analysis_rule/rule_context.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/source/line_info.dart'; import 'package:solid_lints/src/lints/newline_before_return/newline_before_return_rule.dart'; /// Visitor for [NewlineBeforeReturnRule]. class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { final NewlineBeforeReturnRule _rule; - final RuleContext _context; - /// Creates instance of [NewLineBeforeReturnVisitor] with line info - NewLineBeforeReturnVisitor(this._rule, this._context); + NewLineBeforeReturnVisitor(this._rule); @override void visitReturnStatement(ReturnStatement node) { super.visitReturnStatement(node); - final source = _context.allUnits.first.content; - if (!_statementIsInBlock(node)) return; if (_statementIsFirstInBlock(node)) return; - if (_statementHasNewLineBefore(node, source)) return; + if (_statementHasNewLineBefore(node)) return; _rule.reportAtNode(node); } @@ -56,27 +52,31 @@ class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { static bool _statementHasNewLineBefore( ReturnStatement node, - String source, ) { - final previousToken = node.returnKeyword.previous!; + final root = node.root; + if (root is! CompilationUnit) return true; + + final lineInfo = root.lineInfo; - final lastNotEmptyLineToken = _optimalToken(node.returnKeyword, source); + final previousTokenLineNumber = lineInfo + .getLocation(node.returnKeyword.previous!.end) + .lineNumber; + final lastNotEmptyLineToken = _optimalToken(node.returnKeyword, lineInfo); + final tokenLineNumber = lineInfo + .getLocation(lastNotEmptyLineToken.offset) + .lineNumber; - return _hasBlankLineBetween( - previousToken, - lastNotEmptyLineToken, - source, - ); + return tokenLineNumber > previousTokenLineNumber + 1; } /// If return statement has comment above ignores all the comment lines - static Token _optimalToken(Token token, String source) { + static Token _optimalToken(Token token, LineInfo lineInfo) { var optimalToken = token; var commentToken = _latestCommentToken(token); - while (commentToken != null && - !_hasBlankLineBetween(commentToken, optimalToken, source)) { + lineInfo.getLocation(commentToken.end).lineNumber + 1 >= + lineInfo.getLocation(optimalToken.offset).lineNumber) { optimalToken = commentToken; commentToken = commentToken.previous; } @@ -86,25 +86,10 @@ class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { static Token? _latestCommentToken(Token token) { Token? latestCommentToken = token.precedingComments; - - if (latestCommentToken == null) return null; - - while (latestCommentToken!.next != null) { - latestCommentToken = latestCommentToken.next; + while (latestCommentToken?.next != null) { + latestCommentToken = latestCommentToken?.next; } return latestCommentToken; } - - static bool _hasBlankLineBetween(Token a, Token b, String source) { - final aEnd = a.end; - final bStart = b.offset; - - if (aEnd > bStart) return false; - - final between = source.substring(aEnd, bStart); - final hasBlankLine = between.contains(RegExp(r'\n[ \t]*\r?\n')); - - return hasBlankLine; - } } From 00a96f6d6d646760b30707c054fee44f09732d0a Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 14:37:14 +0300 Subject: [PATCH 15/16] Updated newline_before_return visitor from recursive to simple --- .../visitors/newline_before_return_visitor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart index 7e1d6189..a8dda6a8 100644 --- a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart +++ b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart @@ -28,7 +28,7 @@ import 'package:analyzer/source/line_info.dart'; import 'package:solid_lints/src/lints/newline_before_return/newline_before_return_rule.dart'; /// Visitor for [NewlineBeforeReturnRule]. -class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { +class NewLineBeforeReturnVisitor extends SimpleAstVisitor { final NewlineBeforeReturnRule _rule; /// Creates instance of [NewLineBeforeReturnVisitor] with line info From dce639c23cabfc210ecdeb4efa6217bf40c85fa7 Mon Sep 17 00:00:00 2001 From: daria-trusca-solid Date: Thu, 4 Jun 2026 15:02:20 +0300 Subject: [PATCH 16/16] Revert previous changes --- .../newline_before_return_rule.dart | 59 +++---- .../newline_before_return_visitor.dart | 24 +-- .../newline_before_return_rule_test.dart | 146 ------------------ 3 files changed, 43 insertions(+), 186 deletions(-) delete mode 100644 test/lints/newline_before_return/newline_before_return_rule_test.dart diff --git a/lib/src/lints/newline_before_return/newline_before_return_rule.dart b/lib/src/lints/newline_before_return/newline_before_return_rule.dart index 68e8360e..3624a7c6 100644 --- a/lib/src/lints/newline_before_return/newline_before_return_rule.dart +++ b/lib/src/lints/newline_before_return/newline_before_return_rule.dart @@ -21,11 +21,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -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:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:solid_lints/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart'; +import 'package:solid_lints/src/models/rule_config.dart'; +import 'package:solid_lints/src/models/solid_lint_rule.dart'; // Inspired by ESLint (https://eslint.org/docs/rules/newline-before-return) @@ -72,35 +72,38 @@ import 'package:solid_lints/src/lints/newline_before_return/visitors/newline_bef /// } /// } /// ``` -class NewlineBeforeReturnRule extends AnalysisRule { - /// The name of the lint rule. - static const String _lintName = 'newline_before_return'; +class NewlineBeforeReturnRule extends SolidLintRule { + /// This lint rule represents the error if + /// newline is missing before return statement + static const String lintName = 'newline_before_return'; - /// The message shown when the lint is triggered. - static const String _lintMessage = 'Missing blank line before return.'; + NewlineBeforeReturnRule._(super.config); - /// Lint code for this rule. - static const LintCode _code = LintCode( - _lintName, - _lintMessage, - ); + /// Creates a new instance of [NewlineBeforeReturnRule] + /// based on the lint configuration. + factory NewlineBeforeReturnRule.createRule(CustomLintConfigs configs) { + final rule = RuleConfig( + configs: configs, + name: lintName, + problemMessage: (value) => "Missing blank line before return.", + ); - /// Creates a new instance of [NewlineBeforeReturnRule]. - NewlineBeforeReturnRule() - : super( - name: _lintName, - description: _lintMessage, - ); - - @override - LintCode get diagnosticCode => _code; + return NewlineBeforeReturnRule._(rule); + } @override - void registerNodeProcessors( - RuleVisitorRegistry registry, - RuleContext context, + void run( + CustomLintResolver resolver, + DiagnosticReporter reporter, + CustomLintContext context, ) { - final visitor = NewLineBeforeReturnVisitor(this); - registry.addReturnStatement(this, visitor); + context.registry.addReturnStatement((node) { + final visitor = NewLineBeforeReturnVisitor(resolver.lineInfo); + visitor.visitReturnStatement(node); + + for (final element in visitor.statements) { + reporter.atNode(element, code); + } + }); } } diff --git a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart index a8dda6a8..febdad5c 100644 --- a/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart +++ b/lib/src/lints/newline_before_return/visitors/newline_before_return_visitor.dart @@ -25,14 +25,17 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/source/line_info.dart'; -import 'package:solid_lints/src/lints/newline_before_return/newline_before_return_rule.dart'; -/// Visitor for [NewlineBeforeReturnRule]. -class NewLineBeforeReturnVisitor extends SimpleAstVisitor { - final NewlineBeforeReturnRule _rule; +/// The AST visitor that will all return statements. +class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { + final LineInfo _lineInfo; + final _statements = []; /// Creates instance of [NewLineBeforeReturnVisitor] with line info - NewLineBeforeReturnVisitor(this._rule); + NewLineBeforeReturnVisitor(this._lineInfo); + + /// List of all return statements + Iterable get statements => _statements; @override void visitReturnStatement(ReturnStatement node) { @@ -40,9 +43,9 @@ class NewLineBeforeReturnVisitor extends SimpleAstVisitor { if (!_statementIsInBlock(node)) return; if (_statementIsFirstInBlock(node)) return; - if (_statementHasNewLineBefore(node)) return; + if (_statementHasNewLineBefore(node, _lineInfo)) return; - _rule.reportAtNode(node); + _statements.add(node); } static bool _statementIsInBlock(ReturnStatement node) => node.parent is Block; @@ -52,15 +55,12 @@ class NewLineBeforeReturnVisitor extends SimpleAstVisitor { static bool _statementHasNewLineBefore( ReturnStatement node, + LineInfo lineInfo, ) { - final root = node.root; - if (root is! CompilationUnit) return true; - - final lineInfo = root.lineInfo; - final previousTokenLineNumber = lineInfo .getLocation(node.returnKeyword.previous!.end) .lineNumber; + final lastNotEmptyLineToken = _optimalToken(node.returnKeyword, lineInfo); final tokenLineNumber = lineInfo .getLocation(lastNotEmptyLineToken.offset) diff --git a/test/lints/newline_before_return/newline_before_return_rule_test.dart b/test/lints/newline_before_return/newline_before_return_rule_test.dart deleted file mode 100644 index 34ee9760..00000000 --- a/test/lints/newline_before_return/newline_before_return_rule_test.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; -import 'package:solid_lints/src/lints/newline_before_return/newline_before_return_rule.dart'; -import 'package:test_reflective_loader/test_reflective_loader.dart'; - -void main() { - defineReflectiveSuite(() { - defineReflectiveTests(NewlineBeforeReturnRuleTest); - }); -} - -@reflectiveTest -class NewlineBeforeReturnRuleTest extends AnalysisRuleTest { - @override - void setUp() { - rule = NewlineBeforeReturnRule(); - super.setUp(); - } - - @override - String get analysisRule => rule.name; - - void test_reports_no_newline_before_return_value() async { - await assertDiagnostics( - r''' -int method() { - final a = 0; - return 1; -} - ''', - [lint(32, 9)], - ); - } - - void test_reports_no_newline_before_return() async { - await assertDiagnostics( - r''' -void method() { - final a = 0; - return; -} - ''', - [lint(33, 7)], - ); - } - - void test_reports_no_newline_before_return_with_comment() async { - await assertDiagnostics( - r''' -void method() { - final a = 0; - // Comment - return; -} - ''', - [lint(46, 7)], - ); - } - - void test_does_not_report_no_newline_before_single_statement_return() async { - await assertNoDiagnostics(r''' -void method() { - return; -} - '''); - } - - void test_does_not_report_newline_before_return() async { - await assertNoDiagnostics(r''' -void method() { - final a = 0; - - return; -} - '''); - } - - void - test_does_not_report_no_newline_before_single_statement_return_value() async { - await assertNoDiagnostics(r''' -int method() { - return 1; -} - '''); - } - - void - test_does_not_report_no_newline_before_single_statement_nested_return() async { - await assertNoDiagnostics(r''' -class Foo{ - void bar(void Function()) { - return; - } -} - -void fun() { - final foo = Foo(); - foo.bar(() { - return; - }); -} - '''); - } - - void test_reports_no_newline_before_return_nested() async { - await assertDiagnostics( - r''' -class Foo{ - void bar(void Function()) { - return; - } -} - -void fun() { - final foo = Foo(); - foo.bar(() { - final a = 1; - return; - }); -} - ''', - [lint(130, 7)], - ); - } - - void test_reports_no_newline_before_two_return_nested() async { - await assertDiagnostics( - r''' -class Foo{ - void bar(void Function()) { - return; - } -} - -void fun() { - final foo = Foo(); - foo.bar(() { - final a = 1; - return; - }); - return; -} - ''', - [lint(130, 7), lint(146, 7)], - ); - } -}