<commit_before># encoding: utf-8
# frozen_string_literal: true

module RuboCop
  module Cop
    # This force provides a way to track local variables and scopes of Ruby.
    # Cops interact with this force need to override some of the hook methods.
    #
    #     def before_entering_scope(scope, variable_table)
    #     end
    #
    #     def after_entering_scope(scope, variable_table)
    #     end
    #
    #     def before_leaving_scope(scope, variable_table)
    #     end
    #
    #     def after_leaving_scope(scope, variable_table)
    #     end
    #
    #     def before_declaring_variable(variable, variable_table)
    #     end
    #
    #     def after_declaring_variable(variable, variable_table)
    #     end
    class VariableForce < Force # rubocop:disable Metrics/ClassLength
      VARIABLE_ASSIGNMENT_TYPE = :lvasgn
      REGEXP_NAMED_CAPTURE_TYPE = :match_with_lvasgn
      VARIABLE_ASSIGNMENT_TYPES =
        [VARIABLE_ASSIGNMENT_TYPE, REGEXP_NAMED_CAPTURE_TYPE].freeze

      ARGUMENT_DECLARATION_TYPES = [
        :arg, :optarg, :restarg,
        :kwarg, :kwoptarg, :kwrestarg,
        :blockarg, # This doesn't mean block argument, it's block-pass (&block).
        :shadowarg # This means block local variable (obj.each { |arg; this| }).
      ].freeze

      LOGICAL_OPERATOR_ASSIGNMENT_TYPES = [:or_asgn, :and_asgn].freeze
      OPERATOR_ASSIGNMENT_TYPES =
        (LOGICAL_OPERATOR_ASSIGNMENT_TYPES + [:op_asgn]).freeze

      MULTIPLE_ASSIGNMENT_TYPE = :masgn

      VARIABLE_REFERENCE_TYPE = :lvar

      POST_CONDITION_LOOP_TYPES = [:while_post, :until_post].freeze
      LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + [:while, :until, :for]).freeze

      RESCUE_TYPE = :rescue

      ZERO_ARITY_SUPER_TYPE = :zsuper

      TWISTED_SCOPE_TYPES = [:block, :class, :sclass, :defs].freeze
      SCOPE_TYPES = (TWISTED_SCOPE_TYPES + [:module, :def]).freeze

      SEND_TYPE = :send

      VariableReference = Struct.new(:name) do
        def assignment?
          false
        end
      end

      AssignmentReference = Struct.new(:node) do
        def assignment?
          true
        end
      end

      def variable_table
        @variable_table ||= VariableTable.new(self)
      end

      # Starting point.
      def investigate(processed_source)
        root_node = processed_source.ast
        return unless root_node

        variable_table.push_scope(root_node)
        process_node(root_node)
        variable_table.pop_scope
      end

      # This is called for each scope recursively.
      def inspect_variables_in_scope(scope_node)
        variable_table.push_scope(scope_node)
        process_children(scope_node)
        variable_table.pop_scope
      end

      def process_children(origin_node)
        origin_node.each_child_node do |child_node|
          next if scanned_node?(child_node)
          process_node(child_node)
        end
      end

      def process_node(node)
        catch(:skip_children) do
          dispatch_node(node)
          process_children(node)
        end
      end

      def skip_children!
        throw :skip_children
      end

      # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
      def dispatch_node(node)
        case node.type
        when VARIABLE_ASSIGNMENT_TYPE
          process_variable_assignment(node)
        when REGEXP_NAMED_CAPTURE_TYPE
          process_regexp_named_captures(node)
        when MULTIPLE_ASSIGNMENT_TYPE
          process_variable_multiple_assignment(node)
        when VARIABLE_REFERENCE_TYPE
          process_variable_referencing(node)
        when RESCUE_TYPE
          process_rescue(node)
        when ZERO_ARITY_SUPER_TYPE
          process_zero_arity_super(node)
        when SEND_TYPE
          process_send(node)
        when *ARGUMENT_DECLARATION_TYPES
          process_variable_declaration(node)
        when *OPERATOR_ASSIGNMENT_TYPES
          process_variable_operator_assignment(node)
        when *LOOP_TYPES
          process_loop(node)
        when *SCOPE_TYPES
          process_scope(node)
        end
      end
      # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity

      def process_variable_declaration(node)
        variable_name = node.children.first

        # restarg and kwrestarg would have no name:
        #
        #   def initialize(*)
        #   end
        return unless variable_name

        variable_table.declare_variable(variable_name, node)
      end

      def process_variable_assignment(node)
        name = node.children.first

        unless variable_table.variable_exist?(name)
          variable_table.declare_variable(name, node)
        end

        # Need to scan rhs before assignment so that we can mark previous
        # assignments as referenced if rhs has referencing to the variable
        # itself like:
        #
        #   foo = 1
        #   foo = foo + 1
        process_children(node)

        variable_table.assign_to_variable(name, node)

        skip_children!
      end

      def process_regexp_named_captures(node)
        regexp_node, rhs_node = *node

        regexp_string = regexp_node.children[0].children[0] || ''
        regexp = Regexp.new(regexp_string)
        variable_names = regexp.named_captures.keys

        variable_names.each do |name|
          next if variable_table.variable_exist?(name)
          variable_table.declare_variable(name, node)
        end

        process_node(rhs_node)
        process_node(regexp_node)

        variable_names.each do |name|
          variable_table.assign_to_variable(name, node)
        end

        skip_children!
      end
<commit_msg>Reduce complexity in VariableForce<commit_after># encoding: utf-8
# frozen_string_literal: true

module RuboCop
  module Cop
    # This force provides a way to track local variables and scopes of Ruby.
    # Cops interact with this force need to override some of the hook methods.
    #
    #     def before_entering_scope(scope, variable_table)
    #     end
    #
    #     def after_entering_scope(scope, variable_table)
    #     end
    #
    #     def before_leaving_scope(scope, variable_table)
    #     end
    #
    #     def after_leaving_scope(scope, variable_table)
    #     end
    #
    #     def before_declaring_variable(variable, variable_table)
    #     end
    #
    #     def after_declaring_variable(variable, variable_table)
    #     end
    class VariableForce < Force # rubocop:disable Metrics/ClassLength
      VARIABLE_ASSIGNMENT_TYPE = :lvasgn
      REGEXP_NAMED_CAPTURE_TYPE = :match_with_lvasgn
      VARIABLE_ASSIGNMENT_TYPES =
        [VARIABLE_ASSIGNMENT_TYPE, REGEXP_NAMED_CAPTURE_TYPE].freeze

      ARGUMENT_DECLARATION_TYPES = [
        :arg, :optarg, :restarg,
        :kwarg, :kwoptarg, :kwrestarg,
        :blockarg, # This doesn't mean block argument, it's block-pass (&block).
        :shadowarg # This means block local variable (obj.each { |arg; this| }).
      ].freeze

      LOGICAL_OPERATOR_ASSIGNMENT_TYPES = [:or_asgn, :and_asgn].freeze
      OPERATOR_ASSIGNMENT_TYPES =
        (LOGICAL_OPERATOR_ASSIGNMENT_TYPES + [:op_asgn]).freeze

      MULTIPLE_ASSIGNMENT_TYPE = :masgn

      VARIABLE_REFERENCE_TYPE = :lvar

      POST_CONDITION_LOOP_TYPES = [:while_post, :until_post].freeze
      LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + [:while, :until, :for]).freeze

      RESCUE_TYPE = :rescue

      ZERO_ARITY_SUPER_TYPE = :zsuper

      TWISTED_SCOPE_TYPES = [:block, :class, :sclass, :defs].freeze
      SCOPE_TYPES = (TWISTED_SCOPE_TYPES + [:module, :def]).freeze

      SEND_TYPE = :send

      VariableReference = Struct.new(:name) do
        def assignment?
          false
        end
      end

      AssignmentReference = Struct.new(:node) do
        def assignment?
          true
        end
      end

      def variable_table
        @variable_table ||= VariableTable.new(self)
      end

      # Starting point.
      def investigate(processed_source)
        root_node = processed_source.ast
        return unless root_node

        variable_table.push_scope(root_node)
        process_node(root_node)
        variable_table.pop_scope
      end

      def process_node(node)
        catch(:skip_children) do
          dispatch_node(node)
          process_children(node)
        end
      end

      private

      # This is called for each scope recursively.
      def inspect_variables_in_scope(scope_node)
        variable_table.push_scope(scope_node)
        process_children(scope_node)
        variable_table.pop_scope
      end

      def process_children(origin_node)
        origin_node.each_child_node do |child_node|
          next if scanned_node?(child_node)
          process_node(child_node)
        end
      end

      def skip_children!
        throw :skip_children
      end

      # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
      def dispatch_node(node)
        case node.type
        when VARIABLE_ASSIGNMENT_TYPE
          process_variable_assignment(node)
        when REGEXP_NAMED_CAPTURE_TYPE
          process_regexp_named_captures(node)
        when MULTIPLE_ASSIGNMENT_TYPE
          process_variable_multiple_assignment(node)
        when VARIABLE_REFERENCE_TYPE
          process_variable_referencing(node)
        when RESCUE_TYPE
          process_rescue(node)
        when ZERO_ARITY_SUPER_TYPE
          process_zero_arity_super(node)
        when SEND_TYPE
          process_send(node)
        when *ARGUMENT_DECLARATION_TYPES
          process_variable_declaration(node)
        when *OPERATOR_ASSIGNMENT_TYPES
          process_variable_operator_assignment(node)
        when *LOOP_TYPES
          process_loop(node)
        when *SCOPE_TYPES
          process_scope(node)
        end
      end
      # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity

      def process_variable_declaration(node)
        variable_name = node.children.first

        # restarg and kwrestarg would have no name:
        #
        #   def initialize(*)
        #   end
        return unless variable_name

        variable_table.declare_variable(variable_name, node)
      end

      def process_variable_assignment(node)
        name = node.children.first

        unless variable_table.variable_exist?(name)
          variable_table.declare_variable(name, node)
        end

        # Need to scan rhs before assignment so that we can mark previous
        # assignments as referenced if rhs has referencing to the variable
        # itself like:
        #
        #   foo = 1
        #   foo = foo + 1
        process_children(node)

        variable_table.assign_to_variable(name, node)

        skip_children!
      end

      def process_regexp_named_captures(node)
        regexp_node, rhs_node = *node
        variable_names = regexp_captured_names(regexp_node)

        variable_names.each do |name|
          next if variable_table.variable_exist?(name)
          variable_table.declare_variable(name, node)
        end

        process_node(rhs_node)
        process_node(regexp_node)

        variable_names.each do |name|
          variable_table.assign_to_variable(name, node)
        end

        skip_children!
      end

      def regexp_captured_names(node)
        regexp_string = node.children[0].children[0] || ''
        regexp = Regexp.new(regexp_string)

        regexp.named_captures.keys
      end

{'fraction_non_alphanumeric': 0.06322999912180557, 'fraction_numerical': 0.0008781944322472996, 'mean_word_length': 2.4126460893017683, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6698527', 'n_tokens_mistral': 3542, 'n_tokens_neox': 3446, 'n_words': 686}