@@ -11,10 +11,6 @@ module Rails
1111 # This cop works best when used together with
1212 # `Style/FileRead`, `Style/FileWrite` and `Rails/RootJoinChain`.
1313 #
14- # @safety
15- # This cop is unsafe for autocorrection because `Dir`'s `children`, `each_child`, `entries`, and `glob`
16- # methods return string element, but these methods of `Pathname` return `Pathname` element.
17- #
1814 # @example
1915 # # bad
2016 # File.open(Rails.root.join('db', 'schema.rb'))
@@ -32,17 +28,20 @@ module Rails
3228 # Rails.root.join('db', 'schema.rb').write(content)
3329 # Rails.root.join('db', 'schema.rb').binwrite(content)
3430 #
35- class RootPathnameMethods < Base
31+ class RootPathnameMethods < Base # rubocop:disable Metrics/ClassLength
3632 extend AutoCorrector
3733 include RangeHelp
3834
3935 MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
4036
41- DIR_METHODS = %i[ children delete each_child empty? entries exist? glob mkdir open rmdir unlink ] . to_set . freeze
37+ DIR_NON_PATHNAMES_RETURNED_METHODS = %i[ delete empty? exist? mkdir open rmdir unlink ] . to_set . freeze
38+
39+ DIR_PATHNAMES_RETURNED_METHODS = %i[ children each_child entries glob ] . to_set . freeze
4240
43- FILE_METHODS = %i[
41+ DIR_METHODS = ( DIR_PATHNAMES_RETURNED_METHODS + DIR_NON_PATHNAMES_RETURNED_METHODS ) . freeze
42+
43+ FILE_NON_PATHNAME_RETURNED_METHODS = %i[
4444 atime
45- basename
4645 binread
4746 binwrite
4847 birthtime
@@ -53,19 +52,16 @@ class RootPathnameMethods < Base
5352 ctime
5453 delete
5554 directory?
56- dirname
5755 empty?
5856 executable?
5957 executable_real?
6058 exist?
61- expand_path
6259 extname
6360 file?
6461 fnmatch
6562 fnmatch?
6663 ftype
6764 grpowned?
68- join
6965 lchmod
7066 lchown
7167 lstat
@@ -77,9 +73,6 @@ class RootPathnameMethods < Base
7773 readable?
7874 readable_real?
7975 readlines
80- readlink
81- realdirpath
82- realpath
8376 rename
8477 setgid?
8578 setuid?
@@ -102,6 +95,18 @@ class RootPathnameMethods < Base
10295 zero?
10396 ] . to_set . freeze
10497
98+ FILE_PATHNAME_RETURNED_METHODS = %i[
99+ basename
100+ dirname
101+ expand_path
102+ join
103+ readlink
104+ realdirpath
105+ realpath
106+ ] . to_set . freeze
107+
108+ FILE_METHODS = ( FILE_PATHNAME_RETURNED_METHODS + FILE_NON_PATHNAME_RETURNED_METHODS ) . freeze
109+
105110 FILE_TEST_METHODS = %i[
106111 blockdev?
107112 chardev?
@@ -160,13 +165,24 @@ class RootPathnameMethods < Base
160165 (send (const {nil? cbase} :Rails) {:root :public_path})
161166 PATTERN
162167
168+ # @!method dir_pathnames_returned_method?(node)
169+ def_node_matcher :dir_pathnames_returned_method? , <<~PATTERN
170+ (send (const {nil? cbase} :Dir) DIR_PATHNAMES_RETURNED_METHODS ...)
171+ PATTERN
172+
173+ # @!method file_pathname_returned_method?(node)
174+ def_node_matcher :file_pathname_returned_method? , <<~PATTERN
175+ (send (const {nil? cbase} {:IO :File}) FILE_PATHNAME_RETURNED_METHODS ...)
176+ PATTERN
177+
163178 def on_send ( node )
164179 evidence ( node ) do |method , path , args , rails_root |
165180 add_offense ( node , message : format ( MSG , method : method , rails_root : rails_root . source ) ) do |corrector |
181+ suffix = build_replacement_suffix ( node )
166182 replacement = if dir_glob? ( node )
167- build_path_glob_replacement ( path , method )
183+ build_path_glob_replacement ( path , method , suffix )
168184 else
169- build_path_replacement ( path , method , args )
185+ build_path_replacement ( path , method , args , suffix )
170186 end
171187
172188 corrector . replace ( node , replacement )
@@ -183,15 +199,15 @@ def evidence(node)
183199 yield ( method , path , args , rails_root )
184200 end
185201
186- def build_path_glob_replacement ( path , method )
202+ def build_path_glob_replacement ( path , method , suffix )
187203 receiver = range_between ( path . source_range . begin_pos , path . children . first . loc . selector . end_pos ) . source
188204
189205 argument = path . arguments . one? ? path . first_argument . source : join_arguments ( path . arguments )
190206
191- "#{ receiver } .#{ method } (#{ argument } )"
207+ "#{ receiver } .#{ method } (#{ argument } )#{ suffix } "
192208 end
193209
194- def build_path_replacement ( path , method , args )
210+ def build_path_replacement ( path , method , args , suffix )
195211 path_replacement = path . source
196212 if path . arguments? && !path . parenthesized_call?
197213 path_replacement [ ' ' ] = '('
@@ -200,9 +216,20 @@ def build_path_replacement(path, method, args)
200216
201217 replacement = "#{ path_replacement } .#{ method } "
202218 replacement += "(#{ args . map ( &:source ) . join ( ', ' ) } )" unless args . empty?
219+ replacement += suffix
203220 replacement
204221 end
205222
223+ def build_replacement_suffix ( node )
224+ if dir_pathnames_returned_method? ( node )
225+ '.map(&:to_s)'
226+ elsif file_pathname_returned_method? ( node )
227+ '.to_s'
228+ else
229+ ''
230+ end
231+ end
232+
206233 def include_interpolation? ( arguments )
207234 arguments . any? do |argument |
208235 argument . children . any? { |child | child . respond_to? ( :begin_type? ) && child . begin_type? }
0 commit comments