Skip to content

Commit d130dff

Browse files
committed
Check for one line methods, simplify assessment EN14960 bits
1 parent 8ec7d5f commit d130dff

File tree

7 files changed

+366
-130
lines changed

7 files changed

+366
-130
lines changed

.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ inherit_gem: { rubocop-rails-omakase: rubocop.yml }
44
# Load custom cops
55
require:
66
- ./lib/rubocop/cop/custom/ternary_line_breaks
7+
- ./lib/rubocop/cop/custom/one_line_methods
78

89
# Load plugins
910
plugins:
@@ -13,6 +14,9 @@ plugins:
1314
Custom/TernaryLineBreaks:
1415
Enabled: true
1516

17+
Custom/OneLineMethods:
18+
Enabled: true
19+
1620
# Enable Sorbet cops
1721
Sorbet/BlockMethodDefinition:
1822
Enabled: true

app/models/assessments/anchorage_assessment.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ def meets_anchor_requirements?
5858
end
5959

6060
sig { returns(Integer) }
61-
def total_anchors
62-
(num_low_anchors || 0) + (num_high_anchors || 0)
63-
end
61+
def total_anchors = (num_low_anchors || 0) + (num_high_anchors || 0)
6462

6563
sig { returns(Integer) }
6664
def required_anchors

app/models/assessments/user_height_assessment.rb

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,31 @@ class UserHeightAssessment < ApplicationRecord
5050

5151
sig { returns(T::Hash[Symbol, T.untyped]) }
5252
def validate_play_area
53-
return {valid: false, errors: ["Inspection not found"]} unless inspection
54-
5553
unit_length = inspection.length
5654
unit_width = inspection.width
5755

58-
# Check if we have all required measurements
59-
if [unit_length, unit_width, play_area_length, play_area_width].any?(&:nil?)
60-
return {
61-
valid: false,
62-
errors: ["Missing required measurements for play area validation"],
63-
measurements: {}
64-
}
65-
end
56+
measurements = [
57+
unit_length,
58+
unit_width,
59+
play_area_length,
60+
play_area_width
61+
]
62+
return missing_measurements_result if measurements.any?(&:nil?)
63+
64+
call_en14960_validator(unit_length, unit_width)
65+
end
6666

67-
# Use the negative_adjustment value, defaulting to 0 if nil
67+
private
68+
69+
sig {
70+
params(
71+
unit_length: T.nilable(BigDecimal),
72+
unit_width: T.nilable(BigDecimal)
73+
).returns(T::Hash[Symbol, T.untyped])
74+
}
75+
def call_en14960_validator(unit_length, unit_width)
6876
adjustment = negative_adjustment || 0
6977

70-
# Call the EN14960 validator - convert BigDecimal to Float
7178
EN14960.validate_play_area(
7279
unit_length: unit_length.to_f,
7380
unit_width: unit_width.to_f,
@@ -77,9 +84,13 @@ def validate_play_area
7784
)
7885
end
7986

80-
sig { returns(T::Boolean) }
81-
def play_area_valid?
82-
validate_play_area[:valid]
87+
sig { returns(T::Hash[Symbol, T.untyped]) }
88+
def missing_measurements_result
89+
{
90+
valid: false,
91+
errors: [I18n.t("assessments.user_height.errors.missing_measurements")],
92+
measurements: {}
93+
}
8394
end
8495
end
8596
end
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# typed: false
2+
# frozen_string_literal: true
3+
4+
module RuboCop
5+
module Cop
6+
module Custom
7+
# Detects one-line methods that are simple aliases
8+
# (passing same arguments through)
9+
# These should call the original method directly instead
10+
#
11+
# @example
12+
# # bad - unnecessary wrapper method
13+
# def user_name(id)
14+
# fetch_user_name(id)
15+
# end
16+
#
17+
# # good - just call fetch_user_name directly
18+
#
19+
# # good - performs calculation
20+
# def total_anchors
21+
# (num_low_anchors || 0) + (num_high_anchors || 0)
22+
# end
23+
#
24+
# # good - calls with different arguments
25+
# def full_name
26+
# format_name(first_name, last_name)
27+
# end
28+
#
29+
class OneLineMethods < Base
30+
MSG = "Call the original method directly instead of creating " \
31+
"an aliasing wrapper method"
32+
33+
def on_def(node)
34+
return unless aliasing_method?(node)
35+
36+
add_offense(node)
37+
end
38+
39+
def on_defs(node)
40+
return unless aliasing_method?(node)
41+
42+
add_offense(node)
43+
end
44+
45+
private
46+
47+
def aliasing_method?(node)
48+
body = node.body
49+
return false unless body
50+
return false unless body.send_type?
51+
return false if body.receiver
52+
53+
method_args = node.arguments
54+
call_args = body.arguments
55+
56+
return false unless method_args.size == call_args.size
57+
return false if method_args.empty?
58+
59+
arguments_match?(method_args, call_args)
60+
end
61+
62+
def arguments_match?(method_args, call_args)
63+
method_args.zip(call_args).all? do |method_arg, call_arg|
64+
call_arg.lvar_type? && call_arg.children.first == method_arg.name
65+
end
66+
end
67+
end
68+
end
69+
end
70+
end
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# typed: false
2+
# frozen_string_literal: true
3+
4+
require "rails_helper"
5+
require "rubocop"
6+
require "rubocop/rspec/support"
7+
8+
# Load the custom cop using Rails.root
9+
require Rails.root.join("lib/rubocop/cop/custom/one_line_methods")
10+
11+
RSpec.describe RuboCop::Cop::Custom::OneLineMethods, type: :rubocop do
12+
include RuboCop::RSpec::ExpectOffense
13+
14+
let(:config) { RuboCop::Config.new }
15+
let(:cop) { described_class.new(config) }
16+
17+
context "when method is a simple alias with same arguments" do
18+
it "registers an offense for single argument pass-through" do
19+
message = "Custom/OneLineMethods: Call the original method directly " \
20+
"instead of creating an aliasing wrapper method"
21+
expect_offense(<<~RUBY)
22+
def fetch_user(id)
23+
^^^^^^^^^^^^^^^^^^ #{message}
24+
get_user(id)
25+
end
26+
RUBY
27+
end
28+
29+
it "registers an offense for multiple arguments pass-through" do
30+
message = "Custom/OneLineMethods: Call the original method directly " \
31+
"instead of creating an aliasing wrapper method"
32+
expect_offense(<<~RUBY)
33+
def find_record(type, id)
34+
^^^^^^^^^^^^^^^^^^^^^^^^^ #{message}
35+
lookup_record(type, id)
36+
end
37+
RUBY
38+
end
39+
40+
it "registers an offense for class methods" do
41+
message = "Custom/OneLineMethods: Call the original method directly " \
42+
"instead of creating an aliasing wrapper method"
43+
expect_offense(<<~RUBY)
44+
def self.fetch_user(id)
45+
^^^^^^^^^^^^^^^^^^^^^^^ #{message}
46+
get_user(id)
47+
end
48+
RUBY
49+
end
50+
end
51+
52+
context "when method is not a simple alias" do
53+
it "does not register offense for methods with different arguments" do
54+
expect_no_offenses(<<~RUBY)
55+
def full_name
56+
format_name(first_name, last_name)
57+
end
58+
RUBY
59+
end
60+
61+
it "does not register offense for methods without arguments" do
62+
expect_no_offenses(<<~RUBY)
63+
def complete?
64+
incomplete_fields.empty?
65+
end
66+
RUBY
67+
end
68+
69+
it "does not register offense for methods calling on a receiver" do
70+
expect_no_offenses(<<~RUBY)
71+
def user_name
72+
user.name
73+
end
74+
RUBY
75+
end
76+
77+
it "does not register offense for comparison operations" do
78+
expect_no_offenses(<<~RUBY)
79+
def triggered_by?(check_user)
80+
user == check_user
81+
end
82+
RUBY
83+
end
84+
85+
it "does not register offense for calculations" do
86+
expect_no_offenses(<<~RUBY)
87+
def total_anchors
88+
(num_low_anchors || 0) + (num_high_anchors || 0)
89+
end
90+
RUBY
91+
end
92+
93+
it "does not register offense for transformations" do
94+
expect_no_offenses(<<~RUBY)
95+
def column_name_syms
96+
column_names.map(&:to_sym).sort
97+
end
98+
RUBY
99+
end
100+
101+
it "does not register offense for multi-line methods" do
102+
expect_no_offenses(<<~RUBY)
103+
def some_method(arg)
104+
result = process(arg)
105+
result
106+
end
107+
RUBY
108+
end
109+
110+
# Endless methods are handled differently and won't be parsed as regular
111+
# methods so they naturally won't trigger this cop
112+
end
113+
114+
context "edge cases" do
115+
it "does not register offense for methods with blocks" do
116+
expect_no_offenses(<<~RUBY)
117+
def process_items(items)
118+
handle_items(items) { |item| item.process }
119+
end
120+
RUBY
121+
end
122+
123+
it "does not register offense for methods with keyword arguments" do
124+
expect_no_offenses(<<~RUBY)
125+
def create_user(name:, email:)
126+
build_user(name: name, email: email)
127+
end
128+
RUBY
129+
end
130+
131+
it "does not register offense for methods with splat arguments" do
132+
expect_no_offenses(<<~RUBY)
133+
def forward_args(*args)
134+
process(*args)
135+
end
136+
RUBY
137+
end
138+
139+
it "does not register offense when arguments are reordered" do
140+
expect_no_offenses(<<~RUBY)
141+
def swap_args(a, b)
142+
process(b, a)
143+
end
144+
RUBY
145+
end
146+
147+
it "does not register offense when fewer arguments are passed" do
148+
expect_no_offenses(<<~RUBY)
149+
def simplified_call(a, b)
150+
complex_call(a)
151+
end
152+
RUBY
153+
end
154+
155+
it "does not register offense for delegations without arguments" do
156+
expect_no_offenses(<<~RUBY)
157+
def current_user
158+
fetch_current_user
159+
end
160+
RUBY
161+
end
162+
end
163+
end

0 commit comments

Comments
 (0)