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