Skip to content

Commit 59f6f85

Browse files
committed
Add autocorrection for Rails/FilePath
1 parent 2235d8c commit 59f6f85

File tree

3 files changed

+187
-17
lines changed

3 files changed

+187
-17
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#991](https://github.com/rubocop/rubocop-rails/pull/991): Add autocorrection for `Rails/FilePath`. ([@r7kamura][])

lib/rubocop/cop/rails/file_path.rb

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ module Rails
3535
# Rails.root.join('app', 'models', 'goober').to_s
3636
#
3737
class FilePath < Base
38+
extend AutoCorrector
39+
3840
include ConfigurableEnforcedStyle
3941
include RangeHelp
4042

@@ -57,9 +59,9 @@ class FilePath < Base
5759
def on_dstr(node)
5860
return unless rails_root_nodes?(node)
5961
return if dstr_separated_by_colon?(node)
60-
return unless dstr_ending_with_file_extension?(node) || dstr_including_file_separator?(node)
6162

62-
register_offense(node, require_to_s: false)
63+
check_for_slash_after_rails_root_in_dstr(node)
64+
check_for_extension_after_rails_root_join_in_dstr(node)
6365
end
6466

6567
def on_send(node)
@@ -70,11 +72,33 @@ def on_send(node)
7072

7173
private
7274

75+
def check_for_slash_after_rails_root_in_dstr(node)
76+
rails_root_index = find_rails_root_index(node)
77+
slash_node = node.children[rails_root_index + 1]
78+
return unless slash_node&.str_type? && slash_node.source.start_with?(File::SEPARATOR)
79+
80+
register_offense(node, require_to_s: false) do |corrector|
81+
autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
82+
end
83+
end
84+
85+
def check_for_extension_after_rails_root_join_in_dstr(node)
86+
rails_root_index = find_rails_root_index(node)
87+
extension_node = node.children[rails_root_index + 1]
88+
return unless extension_node?(extension_node)
89+
90+
register_offense(node, require_to_s: false) do |corrector|
91+
autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
92+
end
93+
end
94+
7395
def check_for_file_join_with_rails_root(node)
7496
return unless file_join_nodes?(node)
7597
return unless node.arguments.any? { |e| rails_root_nodes?(e) }
7698

77-
register_offense(node, require_to_s: true)
99+
register_offense(node, require_to_s: true) do |corrector|
100+
autocorrect_file_join(corrector, node)
101+
end
78102
end
79103

80104
def check_for_rails_root_join_with_string_arguments(node)
@@ -84,7 +108,9 @@ def check_for_rails_root_join_with_string_arguments(node)
84108
return unless node.arguments.size > 1
85109
return unless node.arguments.all?(&:str_type?)
86110

87-
register_offense(node, require_to_s: false)
111+
register_offense(node, require_to_s: false) do |corrector|
112+
autocorrect_rails_root_join_with_string_arguments(corrector, node)
113+
end
88114
end
89115

90116
def check_for_rails_root_join_with_slash_separated_path(node)
@@ -93,20 +119,22 @@ def check_for_rails_root_join_with_slash_separated_path(node)
93119
return unless rails_root_join_nodes?(node)
94120
return unless node.arguments.any? { |arg| string_with_slash?(arg) }
95121

96-
register_offense(node, require_to_s: false)
122+
register_offense(node, require_to_s: false) do |corrector|
123+
autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
124+
end
97125
end
98126

99127
def string_with_slash?(node)
100-
node.str_type? && node.source.include?('/')
128+
node.str_type? && node.source.include?(File::SEPARATOR)
101129
end
102130

103-
def register_offense(node, require_to_s:)
131+
def register_offense(node, require_to_s:, &block)
104132
line_range = node.loc.column...node.loc.last_column
105133
source_range = source_range(processed_source.buffer, node.first_line, line_range)
106134

107135
message = build_message(require_to_s)
108136

109-
add_offense(source_range, message: message)
137+
add_offense(source_range, message: message, &block)
110138
end
111139

112140
def build_message(require_to_s)
@@ -116,21 +144,94 @@ def build_message(require_to_s)
116144
format(message_template, to_s: to_s)
117145
end
118146

119-
def dstr_ending_with_file_extension?(node)
120-
node.children.last.str_type? && node.children.last.source.start_with?('.')
147+
def dstr_separated_by_colon?(node)
148+
node.children[1..].any? do |child|
149+
child.str_type? && child.source.start_with?(':')
150+
end
121151
end
122152

123-
def dstr_including_file_separator?(node)
124-
node.children.any? do |child|
125-
child.str_type? && child.source.include?(File::SEPARATOR)
153+
def autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
154+
rails_root_node = node.children[rails_root_index].children.first
155+
argument_source = extract_rails_root_join_argument_source(node, rails_root_index)
156+
if rails_root_node.method?(:join)
157+
append_argument(corrector, rails_root_node, argument_source)
158+
else
159+
replace_with_rails_root_join(corrector, rails_root_node, argument_source)
126160
end
161+
node.children[rails_root_index + 1..].each { |child| corrector.remove(child) }
127162
end
128163

129-
def dstr_separated_by_colon?(node)
130-
node.children[1..].any? do |child|
131-
child.str_type? && child.source.start_with?(':')
164+
def autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
165+
rails_root_node = node.children[rails_root_index].children.first
166+
return unless rails_root_node.arguments.last.str_type?
167+
168+
corrector.insert_before(rails_root_node.arguments.last.location.end, extension_node.source)
169+
corrector.remove(extension_node)
170+
end
171+
172+
def autocorrect_file_join(corrector, node)
173+
corrector.replace(node.receiver, 'Rails.root')
174+
corrector.remove(
175+
range_with_surrounding_space(
176+
range_with_surrounding_comma(
177+
node.arguments.first.source_range,
178+
:right
179+
),
180+
side: :right
181+
)
182+
)
183+
corrector.insert_after(node, '.to_s')
184+
end
185+
186+
def autocorrect_rails_root_join_with_string_arguments(corrector, node)
187+
corrector.replace(node.arguments.first, %("#{node.arguments.map(&:value).join('/')}"))
188+
node.arguments[1..].each do |argument|
189+
corrector.remove(
190+
range_with_surrounding_comma(
191+
range_with_surrounding_space(
192+
argument.source_range,
193+
side: :left
194+
),
195+
:left
196+
)
197+
)
198+
end
199+
end
200+
201+
def autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
202+
node.arguments.each do |argument|
203+
next unless string_with_slash?(argument)
204+
205+
index = argument.source.index(File::SEPARATOR)
206+
rest = inner_range_of(argument).adjust(begin_pos: index - 1)
207+
corrector.remove(rest)
208+
corrector.insert_after(argument, %(, "#{rest.source.delete_prefix(File::SEPARATOR)}"))
132209
end
133210
end
211+
212+
def inner_range_of(node)
213+
node.location.end.with(begin_pos: node.location.begin.end_pos).adjust(end_pos: -1)
214+
end
215+
216+
def find_rails_root_index(node)
217+
node.children.index { |child| rails_root_nodes?(child) }
218+
end
219+
220+
def append_argument(corrector, node, argument_source)
221+
corrector.insert_after(node.arguments.last, %(, "#{argument_source}"))
222+
end
223+
224+
def replace_with_rails_root_join(corrector, node, argument_source)
225+
corrector.replace(node, %<Rails.root.join("#{argument_source}")>)
226+
end
227+
228+
def extract_rails_root_join_argument_source(node, rails_root_index)
229+
node.children[rails_root_index + 1..].map(&:source).join.delete_prefix(File::SEPARATOR)
230+
end
231+
232+
def extension_node?(node)
233+
node&.str_type? && node.source.start_with?('.')
234+
end
134235
end
135236
end
136237
end

spec/rubocop/cop/rails/file_path_spec.rb

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
Rails.root.join('app', 'models', 'user.rb')
1111
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to')`.
1212
RUBY
13+
14+
expect_correction(<<~RUBY)
15+
Rails.root.join("app/models/user.rb")
16+
RUBY
1317
end
1418
end
1519

@@ -19,6 +23,10 @@
1923
::Rails.root.join('app', 'models', 'user.rb')
2024
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to')`.
2125
RUBY
26+
27+
expect_correction(<<~RUBY)
28+
::Rails.root.join("app/models/user.rb")
29+
RUBY
2230
end
2331
end
2432

@@ -28,6 +36,10 @@
2836
system "rm -rf #{Rails.root.join('a', 'b.png')}"
2937
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to')`.
3038
RUBY
39+
40+
expect_correction(<<~'RUBY')
41+
system "rm -rf #{Rails.root.join("a/b.png")}"
42+
RUBY
3143
end
3244
end
3345

@@ -61,6 +73,10 @@
6173
File.join(Rails.root, 'app', 'models')
6274
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to').to_s`.
6375
RUBY
76+
77+
expect_correction(<<~RUBY)
78+
Rails.root.join("app/models").to_s
79+
RUBY
6480
end
6581
end
6682

@@ -70,6 +86,10 @@
7086
::File.join(Rails.root, 'app', 'models')
7187
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to').to_s`.
7288
RUBY
89+
90+
expect_correction(<<~RUBY)
91+
Rails.root.join("app/models").to_s
92+
RUBY
7393
end
7494
end
7595

@@ -85,6 +105,10 @@
85105
"#{Rails.root}/app/models/goober"
86106
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to')`.
87107
RUBY
108+
109+
expect_correction(<<~'RUBY')
110+
"#{Rails.root.join("app/models/goober")}"
111+
RUBY
88112
end
89113
end
90114

@@ -94,6 +118,10 @@
94118
"#{Rails.root}/a/#{b}"
95119
^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to')`.
96120
RUBY
121+
122+
expect_correction(<<~'RUBY')
123+
"#{Rails.root.join("a/#{b}")}"
124+
RUBY
97125
end
98126
end
99127

@@ -103,6 +131,10 @@
103131
system "rm -rf #{Rails.root}/foo/bar"
104132
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to')`.
105133
RUBY
134+
135+
expect_correction(<<~'RUBY')
136+
system "rm -rf #{Rails.root.join("foo/bar")}"
137+
RUBY
106138
end
107139
end
108140

@@ -112,6 +144,10 @@
112144
"#{Rails.root.join('tmp', user.id, 'icon')}.png"
113145
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to')`.
114146
RUBY
147+
148+
expect_correction(<<~'RUBY')
149+
"#{Rails.root.join('tmp', user.id, 'icon.png')}"
150+
RUBY
115151
end
116152
end
117153

@@ -121,6 +157,10 @@
121157
foo(bar(File.join(Rails.root, "app", "models")))
122158
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path/to').to_s`.
123159
RUBY
160+
161+
expect_correction(<<~RUBY)
162+
foo(bar(Rails.root.join("app/models").to_s))
163+
RUBY
124164
end
125165
end
126166

@@ -204,6 +244,10 @@
204244
File.join(Rails.root, 'app', 'models')
205245
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path', 'to').to_s`.
206246
RUBY
247+
248+
expect_correction(<<~RUBY)
249+
Rails.root.join('app', 'models').to_s
250+
RUBY
207251
end
208252
end
209253

@@ -213,6 +257,10 @@
213257
Rails.root.join('app/models/goober')
214258
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path', 'to')`.
215259
RUBY
260+
261+
expect_correction(<<~RUBY)
262+
Rails.root.join('app', "models", "goober")
263+
RUBY
216264
end
217265
end
218266

@@ -222,6 +270,10 @@
222270
"#{Rails.root}/app/models/goober"
223271
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path', 'to')`.
224272
RUBY
273+
274+
expect_correction(<<~'RUBY')
275+
"#{Rails.root.join("app", "models", "goober")}"
276+
RUBY
225277
end
226278
end
227279

@@ -231,6 +283,10 @@
231283
system "rm -rf #{Rails.root}/foo/bar"
232284
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path', 'to')`.
233285
RUBY
286+
287+
expect_correction(<<~'RUBY')
288+
system "rm -rf #{Rails.root.join("foo", "bar")}"
289+
RUBY
234290
end
235291
end
236292

@@ -240,6 +296,10 @@
240296
"#{Rails.root.join('tmp', user.id, 'icon')}.png"
241297
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path', 'to')`.
242298
RUBY
299+
300+
expect_correction(<<~'RUBY')
301+
"#{Rails.root.join('tmp', user.id, 'icon.png')}"
302+
RUBY
243303
end
244304
end
245305

@@ -249,15 +309,23 @@
249309
foo(bar(File.join(Rails.root, "app", "models")))
250310
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path', 'to').to_s`.
251311
RUBY
312+
313+
expect_correction(<<~RUBY)
314+
foo(bar(Rails.root.join("app", "models").to_s))
315+
RUBY
252316
end
253317
end
254318

255319
context 'Rails.root.join used as an argument' do
256320
it 'registers an offense once' do
257321
expect_offense(<<~RUBY)
258-
foo(Rails.root.join('app/models'))
322+
foo(Rails.root.join("app/models"))
259323
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Rails.root.join('path', 'to')`.
260324
RUBY
325+
326+
expect_correction(<<~RUBY)
327+
foo(Rails.root.join("app", "models"))
328+
RUBY
261329
end
262330
end
263331

0 commit comments

Comments
 (0)