From bde34f4313db95ada56f17089975c42810e7237b Mon Sep 17 00:00:00 2001 From: Margret Riegert Date: Sun, 29 Dec 2024 08:29:26 -0500 Subject: [PATCH] Add `Typing/MethodReturnTypeRestriction` (#519) * Add `Typing/MethodReturnTypeRestriction` * Refactor specs to put enabled options in contexts * Apply suggestions from code review Co-authored-by: Sijawusz Pur Rahnama * Apply suggestions from code review Co-authored-by: Sijawusz Pur Rahnama * Fix specs * Remove undocumented config option * Apply suggestions from code review Co-authored-by: Sijawusz Pur Rahnama * Fix specs * Update spec messages * Apply suggestions from code review Co-authored-by: Sijawusz Pur Rahnama * Update src/ameba/rule/typing/method_return_type_restriction.cr --------- Co-authored-by: Sijawusz Pur Rahnama --- .../method_return_type_restriction_spec.cr | 122 ++++++++++++++++++ .../typing/method_return_type_restriction.cr | 52 ++++++++ 2 files changed, 174 insertions(+) create mode 100644 spec/ameba/rule/typing/method_return_type_restriction_spec.cr create mode 100644 src/ameba/rule/typing/method_return_type_restriction.cr diff --git a/spec/ameba/rule/typing/method_return_type_restriction_spec.cr b/spec/ameba/rule/typing/method_return_type_restriction_spec.cr new file mode 100644 index 000000000..2880c9a06 --- /dev/null +++ b/spec/ameba/rule/typing/method_return_type_restriction_spec.cr @@ -0,0 +1,122 @@ +require "../../../spec_helper" + +module Ameba::Rule::Typing + subject = MethodReturnTypeRestriction.new + + it "passes if a method has a return type restriction" do + expect_no_issues subject, <<-CRYSTAL + def hello : String + "hello world" + end + + private def hello : String + "hello world" + end + + protected def hello : String + "hello world" + end + CRYSTAL + end + + it "passes if a private or protected method doesn't have a return type restriction" do + expect_no_issues subject, <<-CRYSTAL + private def hello + "hello world" + end + + protected def hello + "hello world" + end + CRYSTAL + end + + it "fails if a public method doesn't have a return type restriction" do + expect_issue subject, <<-CRYSTAL + def hello + # ^^^^^^^ error: Method 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 restriction" do + expect_no_issues rule, <<-CRYSTAL + def hello : String + "hello world" + end + + private def hello : String + "hello world" + end + + protected def hello : String + "hello world" + end + CRYSTAL + end + + it "passes if a protected method doesn't have a return type restriction" do + expect_no_issues rule, <<-CRYSTAL + protected def hello + "hello world" + end + CRYSTAL + end + + it "fails if a public or private method doesn't have a return type restriction" do + expect_issue rule, <<-CRYSTAL + def hello + # ^^^^^^^ error: Method should have a return type restriction + "hello world" + end + + private def hello + # ^^^^^^^^^ error: Method 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 restriction" do + expect_no_issues rule, <<-CRYSTAL + protected def hello : String + "hello world" + end + CRYSTAL + end + + it "passes if a private method doesn't have a return type restriction" do + expect_no_issues rule, <<-CRYSTAL + private def hello + "hello world" + end + CRYSTAL + end + + it "fails if a public or protected method doesn't have a return type restriction" do + expect_issue rule, <<-CRYSTAL + def hello + # ^^^^^^^ error: Method should have a return type restriction + "hello world" + end + + protected def hello + # ^^^^^^^^^ error: Method should have a return type restriction + "hello world" + end + CRYSTAL + end + end + end +end diff --git a/src/ameba/rule/typing/method_return_type_restriction.cr b/src/ameba/rule/typing/method_return_type_restriction.cr new file mode 100644 index 000000000..079dc7667 --- /dev/null +++ b/src/ameba/rule/typing/method_return_type_restriction.cr @@ -0,0 +1,52 @@ +module Ameba::Rule::Typing + # A rule that enforces method definitions have a return type restriction. + # + # For example, this are considered invalid: + # + # ``` + # def hello(name = "World") + # "Hello #{name}" + # end + # ``` + # + # And this is valid: + # + # ``` + # def hello(name = "World") : String + # "Hello #{name}" + # end + # ``` + # + # When the config options `PrivateMethods` and `ProtectedMethods` + # are true, this rule is also applied to private and protected methods, respectively. + # + # YAML configuration example: + # + # ``` + # Typing/MethodReturnTypeRestriction: + # Enabled: false + # PrivateMethods: false + # ProtectedMethods: false + # ``` + class MethodReturnTypeRestriction < Base + properties do + since_version "1.7.0" + description "Recommends that methods have a return type restriction" + enabled false + private_methods false + protected_methods false + end + + MSG = "Method should have a return type restriction" + + def test(source, node : Crystal::Def) + issue_for node, MSG unless valid_return_type?(node) + end + + def valid_return_type?(node : Crystal::ASTNode) : Bool + !!node.return_type || + (node.visibility.private? && !private_methods?) || + (node.visibility.protected? && !protected_methods?) + end + end +end