Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Typing/MethodReturnTypeRestriction #519

Merged
1 change: 1 addition & 0 deletions spec/ameba/base_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module Ameba::Rule
Naming
Performance
Style
Typing
]
end
end
Expand Down
244 changes: 244 additions & 0 deletions spec/ameba/rule/typing/method_return_type_restriction_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
require "../../../spec_helper"

module Ameba::Rule::Typing
subject = MethodReturnTypeRestriction.new

it "passes if a method has a return type" do
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
expect_no_issues subject, <<-CRYSTAL
def hello : String
"hello world"
end

# This method is documented
def hello : String
"hello world"
end

nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
private def hello : String
"hello world"
end

protected def hello : String
"hello world"
end

# :nodoc:
def hello : String
"hello world"
end
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
CRYSTAL
end

it "passes if an undocumented method doesn't have a return type" do
expect_no_issues subject, <<-CRYSTAL
def hello
"hello world"
end

private def hello
"hello world"
end

protected def hello : String
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
"hello world"
end

# :nodoc:
def hello
"hello world"
end
CRYSTAL
end

it "fails if a documented method doesn't have a return type" do
expect_issue subject, <<-CRYSTAL
# This method is documented
def hello
# ^^^^^ error: Methods should have a return type restriction
"hello world"
end
CRYSTAL
end

context "properties" do
context "#private_methods" do
rule = MethodReturnTypeRestriction.new
rule.private_methods = true

it "passes if a method has a return type" do
expect_no_issues rule, <<-CRYSTAL
def hello : String
"hello world"
end

# This method is documented
def hello : String
"hello world"
end

nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
private def hello : String
"hello world"
end

protected def hello : String
"hello world"
end

# :nodoc:
def hello : String
"hello world"
end
CRYSTAL
end

it "passes if an undocumented public or protected method doesn't have a return type" do
expect_no_issues rule, <<-CRYSTAL
def hello
"hello world"
end

protected def hello
"hello world"
end

# :nodoc:
def hello
"hello world"
end
CRYSTAL
end

it "fails if a documented public or private method doesn't have a return type" do
expect_issue rule, <<-CRYSTAL
# This method is documented
def hello
# ^^^^^ error: Methods should have a return type restriction
"hello world"
end

# This method is also documented
private def hello
# ^^^^^ error: Methods should have a return type restriction
"hello world"
end
CRYSTAL
end
end

context "#protected_methods" do
rule = MethodReturnTypeRestriction.new
rule.protected_methods = true

it "passes if a method has a return type" do
expect_no_issues rule, <<-CRYSTAL
def hello : String
"hello world"
end

# This method is documented
def hello : String
"hello world"
end

private def hello : String
"hello world"
end

nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
protected def hello : String
"hello world"
end

# :nodoc:
def hello : String
"hello world"
end
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
CRYSTAL
end

it "passes if an undocumented public or private method doesn't have a return type" do
expect_no_issues rule, <<-CRYSTAL
def hello
"hello world"
end

private def hello
"hello world"
end

# :nodoc:
def hello
"hello world"
end
CRYSTAL
end

it "fails if a documented public or protected method doesn't have a return type" do
expect_issue rule, <<-CRYSTAL
# This method is documented
def hello
# ^^^^^ error: Methods should have a return type restriction
"hello world"
end

# This method is also documented
protected def hello
# ^^^^^ error: Methods should have a return type restriction
"hello world"
end
CRYSTAL
end
end

context "#undocumented" do
rule = MethodReturnTypeRestriction.new
rule.undocumented = true

it "passes if a documented method has a return type" do
expect_no_issues rule, <<-CRYSTAL
# This method is documented
def hello : String
"hello world"
end

# This method is documented
private def hello : String
"hello world"
end

# This method is documented
protected def hello : String
"hello world"
end
CRYSTAL
end

it "passes if undocumented private or protected methods have a return type" do
expect_no_issues rule, <<-CRYSTAL
private def hello
"hello world"
end

protected def hello
"hello world"
end

CRYSTAL
end

it "fails if an undocumented method doesn't have a return type" do
expect_issue rule, <<-CRYSTAL
def hello
# ^^^^^ error: Methods should have a return type restriction
"hello world"
end

# :nodoc:
def hello
# ^^^^^ error: Methods should have a return type restriction
"hello world"
end
CRYSTAL
end
end
end
end
66 changes: 66 additions & 0 deletions src/ameba/rule/typing/method_return_type_restriction.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module Ameba::Rule::Typing
# A rule that enforces method definitions have a return type restriction.
#
# For example, these are considered valid:
#
# ```
# def hello : String
# "hello world"
# end
#
# def listen(a, b) : Int32
# 0
# end
# ```
#
# And these are considered invalid:
#
# ```
# def hello
# "hello world"
# end
#
# def listen(a, b)
# 0
# end
# ```
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
#
# When the config options `PrivateMethods` and `ProtectedMethods`
# are true, this rule is also applied to private and protected methods, respectively.
#
# The config option `Undocumented` controls whether this rule applies to undocumented methods and methods with a `:nodoc:` directive.
#
# YAML configuration example:
#
# ```
# Typing/MethodReturnTypeRestriction:
# Enabled: false
# Undocumented: false
# PrivateMethods: false
# ProtectedMethods: false
# ```
class MethodReturnTypeRestriction < Base
properties do
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
description "Recommends that methods have a return type restriction"
enabled false
undocumented false
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
private_methods false
protected_methods false
end

MSG = "Methods should have a return type restriction"
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved

def test(source, node : Crystal::Def)
return if node.return_type || check_config(node)

issue_for node, MSG, prefer_name_location: true
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
end

def check_config(node : Crystal::ASTNode) : Bool
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
(!private_methods? && node.visibility.private?) ||
(!protected_methods? && node.visibility.protected?) ||
(!undocumented? && (node.doc.nil? || node.doc.try(&.starts_with?(":nodoc:")))) ||
false
end
end
end
Loading