diff --git a/spec/ameba/formatter/todo_formatter_spec.cr b/spec/ameba/formatter/todo_formatter_spec.cr index 3f7a8214..ec9fdf99 100644 --- a/spec/ameba/formatter/todo_formatter_spec.cr +++ b/spec/ameba/formatter/todo_formatter_spec.cr @@ -2,7 +2,7 @@ require "../../spec_helper" require "file_utils" module Ameba - private def with_formatter + private def with_formatter(&) io = IO::Memory.new formatter = Formatter::TODOFormatter.new(io) diff --git a/spec/ameba/rule/lint/missing_block_argument_spec.cr b/spec/ameba/rule/lint/missing_block_argument_spec.cr new file mode 100644 index 00000000..90e9f93d --- /dev/null +++ b/spec/ameba/rule/lint/missing_block_argument_spec.cr @@ -0,0 +1,42 @@ +require "../../../spec_helper" + +module Ameba::Rule::Lint + subject = MissingBlockArgument.new + + describe MissingBlockArgument do + it "passes if the block argument is defined" do + expect_no_issues subject, <<-CRYSTAL + def foo(&) + yield 42 + end + + def bar(&block) + yield 24 + end + + def baz(a, b, c, &block) + yield a, b, c + end + CRYSTAL + end + + it "reports if the block argument is missing" do + expect_issue subject, <<-CRYSTAL + def foo + # ^^^ error: Missing anonymous block argument. Use `&` as an argument name to indicate yielding method. + yield 42 + end + + def bar + # ^^^ error: Missing anonymous block argument. Use `&` as an argument name to indicate yielding method. + yield 24 + end + + def baz(a, b, c) + # ^^^ error: Missing anonymous block argument. Use `&` as an argument name to indicate yielding method. + yield a, b, c + end + CRYSTAL + end + end +end diff --git a/src/ameba/rule/lint/missing_block_argument.cr b/src/ameba/rule/lint/missing_block_argument.cr new file mode 100644 index 00000000..b5420f4d --- /dev/null +++ b/src/ameba/rule/lint/missing_block_argument.cr @@ -0,0 +1,49 @@ +module Ameba::Rule::Lint + # A rule that disallows yielding method definitions without block argument. + # + # For example, this is considered invalid: + # + # ``` + # def foo + # yield 42 + # end + # ``` + # + # And has to be written as the following: + # + # ``` + # def foo(&) + # yield 42 + # end + # ``` + # + # YAML configuration example: + # + # ``` + # Lint/MissingBlockArgument: + # Enabled: true + # ``` + class MissingBlockArgument < Base + include AST::Util + + properties do + description "Disallows yielding method definitions without block argument" + end + + MSG = "Missing anonymous block argument. Use `&` as an argument " \ + "name to indicate yielding method." + + def test(source) + AST::ScopeVisitor.new self, source + end + + def test(source, node : Crystal::Def, scope : AST::Scope) + return if !scope.yields? || node.block_arg + + return unless location = node.name_location + end_location = name_end_location(node) + + issue_for location, end_location, MSG + end + end +end diff --git a/src/ameba/runner.cr b/src/ameba/runner.cr index c2c5ac51..fb2be08a 100644 --- a/src/ameba/runner.cr +++ b/src/ameba/runner.cr @@ -179,7 +179,7 @@ module Ameba private MAX_ITERATIONS = 200 - private def loop_unless_infinite(source, corrected_issues) + private def loop_unless_infinite(source, corrected_issues, &) # Keep track of the state of the source. If a rule modifies the source # and another rule undoes it producing identical source we have an # infinite loop. diff --git a/src/ameba/source/rewriter/action.cr b/src/ameba/source/rewriter/action.cr index 5fa1cc96..a13013c2 100644 --- a/src/ameba/source/rewriter/action.cr +++ b/src/ameba/source/rewriter/action.cr @@ -111,7 +111,7 @@ class Ameba::Source::Rewriter end # Similar to `@children.bsearch_index || size` except allows for a starting point - protected def bsearch_child_index(from = 0) + protected def bsearch_child_index(from = 0, &) size = @children.size (from...size).bsearch { |i| yield @children[i] } || size end