Merge pull request #321 from crystal-ameba/Sija/lint-missing-block-argument-rule

This commit is contained in:
Sijawusz Pur Rahnama 2022-12-20 16:20:06 +01:00 committed by GitHub
commit b78e2aebc5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 3 deletions

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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