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..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 @@ -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,35 @@ 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); } } 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..b2e74cdc 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,17 +25,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. -class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { - final LineInfo _lineInfo; - final _statements = []; +/// Visitor for [NewlineBeforeReturnRule]. +class NewLineBeforeReturnVisitor extends SimpleAstVisitor { + 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 +40,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,14 +52,22 @@ class NewLineBeforeReturnVisitor extends RecursiveAstVisitor { static bool _statementHasNewLineBefore( ReturnStatement node, - LineInfo lineInfo, ) { - final previousTokenLineNumber = - lineInfo.getLocation(node.returnKeyword.previous!.end).lineNumber; + final root = node.root; + if (root is! CompilationUnit) return true; + + final lineInfo = root.lineInfo; + final previousToken = node.returnKeyword.previous; + + if (previousToken == null) return true; + final previousTokenLineNumber = lineInfo + .getLocation(previousToken.end) + .lineNumber; final lastNotEmptyLineToken = _optimalToken(node.returnKeyword, lineInfo); - final tokenLineNumber = - lineInfo.getLocation(lastNotEmptyLineToken.offset).lineNumber; + final tokenLineNumber = lineInfo + .getLocation(lastNotEmptyLineToken.offset) + .lineNumber; return tokenLineNumber > previousTokenLineNumber + 1; } diff --git a/lint_test/newline_before_return_test.dart b/lint_test/newline_before_return_test.dart deleted file mode 100644 index 282057ad..00000000 --- a/lint_test/newline_before_return_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -// ignore_for_file: unused_local_variable, prefer_match_file_name -// ignore_for_file: member_ordering -// ignore_for_file: avoid_unused_parameters - -/// Check the `newline_before_return` rule -class Foo { - int method() { - final a = 0; - // expect_lint: newline_before_return - return 1; - } - - void anotherMethod() { - final a = 1; - // expect_lint: newline_before_return - return; - } - - void bar(void Function()) { - return; - } -} - -void fun() { - final foo = Foo(); - foo.bar(() { - // This comment is ignored and line above is checked to be a newline - return; - }); - foo.bar(() { - final a = 1; - // expect_lint: newline_before_return - return; - }); - // expect_lint: newline_before_return - return; -} 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 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..db805301 --- /dev/null +++ b/test/lints/newline_before_return/newline_before_return_rule_test.dart @@ -0,0 +1,171 @@ +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_reports_no_newline_before_return_with_multiple_comments() async { + await assertDiagnostics( + r''' +void method() { + final a = 0; + // Comment 1 + // Comment 2 + return; +} + ''', + [lint(63, 7)], + ); + } + + void test_does_not_report_newline_before_return_with_comment() async { + await assertNoDiagnostics(r''' +void method() { + final a = 0; + + // Comment + return; +} + '''); + } + + 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)], + ); + } +}