Skip to content

Commit

Permalink
Feature/newline before return (#52)
Browse files Browse the repository at this point in the history
* Add newline-before-return rule

* Fix typos, remove unnecessary negation

* Add tests for newline-before-return

---------

Co-authored-by: Denis Bogatirov <[email protected]>
  • Loading branch information
DenisBogatirov and Denis Bogatirov authored Sep 15, 2023
1 parent ab8d1db commit 7179baa
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 0 deletions.
69 changes: 69 additions & 0 deletions lib/lints/newline_before_return/newline_before_return_rule.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/lints/newline_before_return/newline_before_return_visitor.dart';
import 'package:solid_lints/models/rule_config.dart';
import 'package:solid_lints/models/solid_lint_rule.dart';

// Inspired by ESLint (https://eslint.org/docs/rules/newline-before-return)

/// A `newline-before-return` rule which
/// warns about missing newline before return
class NewlineBeforeReturnRule extends SolidLintRule {
/// The [LintCode] of this lint rule that represents the error if
/// newline is missing before return statement
static const String lintName = 'newline-before-return';

NewlineBeforeReturnRule._(super.config);

/// 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.",
);

return NewlineBeforeReturnRule._(rule);
}

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
final visitor = NewLineBeforeReturnVisitor(resolver.lineInfo);

context.registry.addReturnStatement((node) {
visitor.visitReturnStatement(node);

for (final element in visitor.statements) {
reporter.reportErrorForNode(code, element);
}
});
}
}
93 changes: 93 additions & 0 deletions lib/lints/newline_before_return/newline_before_return_visitor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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/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';

/// The AST visitor that will all return statements.
class NewLineBeforeReturnVisitor extends RecursiveAstVisitor<void> {
final LineInfo _lineInfo;
final _statements = <ReturnStatement>[];

/// Creates instance of [NewLineBeforeReturnVisitor] with line info
NewLineBeforeReturnVisitor(this._lineInfo);

/// List of all return statements
Iterable<ReturnStatement> get statements => _statements;

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

if (!_statementIsInBlock(node)) return;
if (_statementIsFirstInBlock(node)) return;
if (_statementHasNewLineBefore(node, _lineInfo)) return;

_statements.add(node);
}

static bool _statementIsInBlock(ReturnStatement node) => node.parent is Block;

static bool _statementIsFirstInBlock(ReturnStatement node) =>
node.returnKeyword.previous == node.parent?.beginToken;

static bool _statementHasNewLineBefore(
ReturnStatement node,
LineInfo lineInfo,
) {
final previousTokenLineNumber =
lineInfo.getLocation(node.returnKeyword.previous!.end).lineNumber;

final lastNotEmptyLineToken = _optimalToken(node.returnKeyword, lineInfo);
final tokenLineNumber =
lineInfo.getLocation(lastNotEmptyLineToken.offset).lineNumber;

return tokenLineNumber > previousTokenLineNumber + 1;
}

/// If return statement has comment above ignores all the comment lines
static Token _optimalToken(Token token, LineInfo lineInfo) {
var optimalToken = token;

var commentToken = _latestCommentToken(token);
while (commentToken != null &&
lineInfo.getLocation(commentToken.end).lineNumber + 1 >=
lineInfo.getLocation(optimalToken.offset).lineNumber) {
optimalToken = commentToken;
commentToken = commentToken.previous;
}

return optimalToken;
}

static Token? _latestCommentToken(Token token) {
Token? latestCommentToken = token.precedingComments;
while (latestCommentToken?.next != null) {
latestCommentToken = latestCommentToken?.next;
}

return latestCommentToken;
}
}
2 changes: 2 additions & 0 deletions lib/solid_lints.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:solid_lints/lints/avoid_unrelated_type_assertions/avoid_unrelate
import 'package:solid_lints/lints/cyclomatic_complexity/cyclomatic_complexity_metric.dart';
import 'package:solid_lints/lints/double_literal_format/double_literal_format_rule.dart';
import 'package:solid_lints/lints/function_lines_of_code/function_lines_of_code_metric.dart';
import 'package:solid_lints/lints/newline_before_return/newline_before_return_rule.dart';
import 'package:solid_lints/lints/number_of_parameters/number_of_parameters_metric.dart';
import 'package:solid_lints/models/solid_lint_rule.dart';

Expand All @@ -35,6 +36,7 @@ class _SolidLints extends PluginBase {
AvoidUnnecessarySetStateRule.createRule(configs),
AvoidUnnecessaryTypeCastsRule.createRule(configs),
AvoidUnrelatedTypeAssertionsRule.createRule(configs),
NewlineBeforeReturnRule.createRule(configs),
];

// Return only enabled rules
Expand Down
1 change: 1 addition & 0 deletions lint_test/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ custom_lint:
- avoid-unnecessary-type-assertions
- avoid-unnecessary-type-casts
- avoid-unrelated-type-assertions
- newline-before-return
35 changes: 35 additions & 0 deletions lint_test/newline_before_return_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// ignore_for_file: unused_local_variable

/// 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;
}

0 comments on commit 7179baa

Please sign in to comment.