sue445/rubocop-itamae

View on GitHub
lib/rubocop/cop/itamae/cd_in_execute.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
# frozen_string_literal: true

module RuboCop
  module Cop
    module Itamae
      # Check that `cd` in `execute`.
      #
      # @example
      #   # bad
      #   execute 'cd /tmp && rm -rf /tmp/*'
      #
      #   # good
      #   execute 'rm -rf /tmp/*' do
      #     cwd '/tmp'
      #   end
      class CdInExecute < Base
        MSG = "Insert `cwd '%<dir>s'` and remove this."

        def_node_search :find_execute_with_block, <<-PATTERN
          (block
            (send nil? :execute
              (str $_)
            )
            args
            $...
          )
        PATTERN

        def_node_search :find_execute_without_block, <<-PATTERN
          (send nil? :execute
            (str $_)
          )
        PATTERN

        def_node_search :find_command, <<-PATTERN
          (send nil? :command
            (str $_)
          )
        PATTERN

        def on_block(node)
          find_execute_with_block(node) do |name, param_nodes|
            add_offence_for_execute_block_name(node, name)

            param_nodes.compact.each do |param_node|
              find_command(param_node) do |command|
                add_offense_for_command_param(param_node, command)
              end
            end
          end
        end

        def on_send(node)
          return if node.parent&.block_type?

          find_execute_without_block(node) do |name|
            add_offense_for_execute_name(node, name)
          end
        end

        # def autocorrect(node)
        #   if node.block_type?
        #     lambda do |corrector|
        #       find_execute_with_block(node) do |name, param_nodes|
        #         if offence_command?(name)
        #           corrector.remove(cd_location(node.child_nodes.first.child_nodes.first, name))
        #
        #           # TODO: insert cwd
        #           next
        #         end
        #       end
        #     end
        #   elsif node.begin_type?
        #     lambda do |corrector|
        #       find_command(node) do |command|
        #         next unless offence_command?(command)
        #
        #         command_node = node.child_nodes.first.child_nodes.first
        #         corrector.remove(cd_location(command_node, command))
        #
        #         # TODO: insert cwd
        #       end
        #     end
        #   elsif node.send_type?
        #     lambda do |corrector|
        #       find_execute_without_block(node) do |name|
        #         next unless offence_command?(name)
        #
        #         corrector.remove(cd_location(node.child_nodes.first, name))
        #
        #         # TODO: insert cwd
        #       end
        #
        #       find_command(node) do |command|
        #         next unless offence_command?(command)
        #
        #         command_node = node.child_nodes.first
        #         corrector.remove(cd_location(command_node, command))
        #
        #         # TODO: insert cwd
        #       end
        #     end
        #   end
        # end

        private

        def add_offense_for_execute_name(node, name)
          dir = cd_dir_in_command(name)
          return unless dir

          loc = cd_location(node.child_nodes.first, name)
          add_offense(loc, message: format(MSG, dir: dir))
        end

        def add_offence_for_execute_block_name(node, name)
          dir = cd_dir_in_command(name)
          return unless dir

          loc = cd_location(node.child_nodes.first.child_nodes.first, name)
          add_offense(loc, message: format(MSG, dir: dir))
        end

        def add_offense_for_command_param(param_node, command)
          dir = cd_dir_in_command(command)
          return unless dir

          command_node =
            if param_node.child_nodes.first.child_nodes.empty?
              param_node.child_nodes.first
            else
              param_node.child_nodes.first.child_nodes.first
            end

          loc = cd_location(command_node, command)
          add_offense(loc, message: format(MSG, dir: dir))
        end

        def cd_dir_in_command(command)
          dir, = parse_command(command)
          dir
        end

        def parse_command(command)
          command =~ /^\s*cd\s+(.+?)&&(.+)$/
          if Regexp.last_match(1) && Regexp.last_match(2)
            [Regexp.last_match(1).strip, Regexp.last_match(2).strip]
          else
            [nil, nil]
          end
        end

        def cd_location(node, command)
          _, second_command = parse_command(command)
          second_command_pos = command.index(second_command)
          begin_pos = node.loc.begin.end_pos
          end_pos = begin_pos + second_command_pos
          Parser::Source::Range.new(node.loc.expression.source_buffer,
                                    begin_pos, end_pos)
        end

        # def indent_num(node)
        #   node.loc.expression.source_buffer.source =~ /^(\s+)/
        #
        #   return Regexp.last_match(1).length if Regexp.last_match(1)
        #   0
        # end
      end
    end
  end
end