lib/rubocop/cop/rails/file_path.rb



# frozen_string_literal: true

module RuboCop
  module Cop
    module Rails
      # This cop is used to identify usages of file path joining process
      # to use `Rails.root.join` clause.
      #
      # @example
      #  # bad
      #  Rails.root.join('app/models/goober')
      #  File.join(Rails.root, 'app/models/goober')
      #  "#{Rails.root}/app/models/goober"
      #
      #  # good
      #  Rails.root.join('app', 'models', 'goober')
      class FilePath < Cop
        MSG = 'Please use `Rails.root.join(\'path\', \'to\')` instead.'.freeze

        def_node_search :file_join_nodes, <<-PATTERN
          (send (const nil :File) :join ...)
        PATTERN

        def_node_search :file_join_nodes?, <<-PATTERN
          (send (const nil :File) :join ...)
        PATTERN

        def_node_search :rails_root_nodes?, <<-PATTERN
          (send (const nil :Rails) :root)
        PATTERN

        def_node_search :rails_root_join_nodes, <<-PATTERN
          (send (send (const nil :Rails) :root) :join ...)
        PATTERN

        def on_dstr(node)
          return unless rails_root_nodes?(node)
          register_offense(node)
        end

        def on_send(node)
          check_for_file_join_with_rails_root(node)
          check_for_rails_root_join_with_slash_separated_path(node)
        end

        private

        def check_for_file_join_with_rails_root(node)
          return unless file_join_nodes?(node)
          return unless file_join_nodes(node).map(&:method_args)
                                             .flatten
                                             .any? { |e| rails_root_nodes?(e) }

          register_offense(node)
        end

        def check_for_rails_root_join_with_slash_separated_path(node)
          return unless rails_root_nodes?(node)
          return unless rails_root_join_nodes(node).map(&:method_args)
                                                   .flatten
                                                   .any? do |arg|
                                                     arg.source =~ %r{/}
                                                   end

          register_offense(node)
        end

        def register_offense(node)
          line_range = node.loc.column...node.loc.last_column

          add_offense(
            node,
            source_range(processed_source.buffer, node.loc.line, line_range),
            MSG
          )
        end
      end
    end
  end
end