forked from rubocop/rubocop-rails
/
http_positional_arguments.rb
123 lines (103 loc) · 4.05 KB
/
http_positional_arguments.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# frozen_string_literal: true
module RuboCop
module Cop
module Rails
# This cop is used to identify usages of http methods like `get`, `post`,
# `put`, `patch` without the usage of keyword arguments in your tests and
# change them to use keyword args. This cop only applies to Rails >= 5.
# If you are running Rails < 5 you should disable the
# Rails/HttpPositionalArguments cop or set your TargetRailsVersion in your
# .rubocop.yml file to 4.2.
#
# @example
# # bad
# get :new, { user_id: 1}
#
# # good
# get :new, params: { user_id: 1 }
# get :new, **options
class HttpPositionalArguments < Cop
extend TargetRailsVersion
MSG = 'Use keyword arguments instead of ' \
'positional arguments for http call: `%<verb>s`.'
KEYWORD_ARGS = %i[
method params session body flash xhr as headers env to
].freeze
RESTRICT_ON_SEND = %i[get post put patch delete head].freeze
minimum_target_rails_version 5.0
def_node_matcher :http_request?, <<~PATTERN
(send nil? {#{RESTRICT_ON_SEND.map(&:inspect).join(' ')}} !nil? $_ ...)
PATTERN
def_node_matcher :kwsplat_hash?, <<~PATTERN
(hash (kwsplat _))
PATTERN
def on_send(node)
http_request?(node) do |data|
return unless needs_conversion?(data)
add_offense(node, location: :selector,
message: format(MSG, verb: node.method_name))
end
end
# given a pre Rails 5 method: get :new, {user_id: @user.id}, {}
#
# @return lambda of auto correct procedure
# the result should look like:
# get :new, params: { user_id: @user.id }, session: {}
# the http_method is the method used to call the controller
# the controller node can be a symbol, method, object or string
# that represents the path/action on the Rails controller
# the data is the http parameters and environment sent in
# the Rails 5 http call
def autocorrect(node)
lambda do |corrector|
corrector.replace(node.loc.expression, correction(node))
end
end
private
def needs_conversion?(data)
return true unless data.hash_type?
return false if kwsplat_hash?(data)
data.each_pair.none? do |pair|
special_keyword_arg?(pair.key) ||
format_arg?(pair.key) && data.pairs.one?
end
end
def special_keyword_arg?(node)
node.sym_type? && KEYWORD_ARGS.include?(node.value)
end
def format_arg?(node)
node.sym_type? && node.value == :format
end
def convert_hash_data(data, type)
return '' if data.hash_type? && data.empty?
hash_data = if data.hash_type?
format('{ %<data>s }',
data: data.pairs.map(&:source).join(', '))
else
# user supplies an object,
# no need to surround with braces
data.source
end
format(', %<type>s: %<hash_data>s', type: type, hash_data: hash_data)
end
def correction(node)
http_path, *data = *node.arguments
controller_action = http_path.source
params = convert_hash_data(data.first, 'params')
session = convert_hash_data(data.last, 'session') if data.size > 1
format(correction_template(node), name: node.method_name,
action: controller_action,
params: params,
session: session)
end
def correction_template(node)
if parentheses?(node)
'%<name>s(%<action>s%<params>s%<session>s)'
else
'%<name>s %<action>s%<params>s%<session>s'
end
end
end
end
end
end