<commit_before>module DeepCover
  module HasChild
    def self.included(base)
      base.extend ClassMethods
      setup_constants(base)
    end

    def self.setup_constants(subclass)
      subclass.const_set :CHILDREN, {}
      subclass.const_set :CHILDREN_TYPES, {}
    end


    def initialize(*)
      super
      self.validate_children_types(children) rescue binding.pry
    end

    # The block given is used as default value if no matching method is found
    def call_handler name, child, index = child.index
      child_name = self.class.child_index_to_name(index, children.size) rescue binding.pry
      method_name = name % {name: child_name}
      if respond_to?(method_name)
        args = [child] unless method(method_name).arity == 0
        answer = send(method_name, *args)
      end
      answer || yield
    end

    def child_flow_entry_count(child)
      call_handler('%{name}_flow_entry_count', child) { yield }
    end

    def rewrite_child(child)
      call_handler('rewrite_%{name}', child) { yield }
    end

    def validate_children_types(nodes)
      mismatches = self.class.check_children_types(nodes)
      unless mismatches.empty?
        raise TypeError, "Invalid children types for #{self.class}(type: #{self.type}): #{mismatches}"
      end
    end

    module ClassMethods
      def has_child(flow_entry_count: nil, rewrite: nil, remap: nil, rest: false, **h)
        raise "Only one custom named argument" if h.size > 1
        name, types = h.first
        raise "Need a name (symbol)" unless name.is_a?(Symbol)
        if types.is_a? Hash
          type_map = types
          raise "Use either remap or a hash as type but not both" if remap
          remap = -> (child) { type_map[child.type] }
          types = type_map.values
        end
        update_children_const(name, rest: rest)
        define_accessor(name)
        add_runtime_check(name, types)
        define_handler(:"#{name}_flow_entry_count", flow_entry_count)
        define_handler(:"rewrite_#{name}", rewrite)
        define_handler(:"remap_#{name}", remap)
        self
      end

      def has_extra_children(**h)
        has_child(rest: true, **h)
      end

      def child_index_to_name(index, nb_children)
        self::CHILDREN.each do |name, i|
          return name if i == index || (i == index - nb_children) ||
            (i.is_a?(Range) && i.begin <= index && i.end + nb_children >= index)
        end
        raise IndexError, "index #{index} does not correspond to any child of #{self}"
      end

      def check_children_types(nodes)
        types = expected_types(nodes)
        nodes_mismatches(nodes, types)
      end

      # Returns a subclass or the base Node, according to type
      def factory(type, index)
        class_name = type.capitalize
        Node.const_defined?(class_name) ? Node.const_get(class_name) : Node
      end

      # Augment creates a covered node from the child_base_node.
      def augment(child_base_node, covered_code, parent, child_index = 0)
        # Skip children that aren't node themselves (e.g. the `method` child of a :def node)
        return child_base_node unless child_base_node.is_a? Parser::AST::Node
        klass = parent.call_handler('remap_%{name}', child_base_node, child_index) {
          factory(child_base_node.type, child_index)
        }
        klass.new(child_base_node, covered_code, parent, child_index)
      end

      private

      def expected_types(nodes)
        types = self::CHILDREN.flat_map do |name, i|
          type = self::CHILDREN_TYPES[name]
          Array.new(nodes.values_at(i).size, type)
        end
      end

      def nodes_mismatches(nodes, types)
        nodes = nodes.dup
        nodes[nodes.size...types.size] = nil
        nodes.zip(types).reject do |node, type|
          node_matches_type?(node, type)
        end
      end

      def node_matches_type?(node, expected)
        case expected
        when :any
          true<commit_msg>call_handler receives child_name directly<commit_after>module DeepCover
  module HasChild
    def self.included(base)
      base.extend ClassMethods
      setup_constants(base)
    end

    def self.setup_constants(subclass)
      subclass.const_set :CHILDREN, {}
      subclass.const_set :CHILDREN_TYPES, {}
    end


    def initialize(*)
      super
      self.validate_children_types(children) rescue binding.pry
    end

    # The block given is used as default value if no matching method is found
    def call_handler name, child, child_name = nil
      child_name ||= self.class.child_index_to_name(child.index, children.size) rescue binding.pry
      method_name = name % {name: child_name}
      if respond_to?(method_name)
        args = [child] unless method(method_name).arity == 0
        answer = send(method_name, *args)
      end
      answer || yield
    end

    def child_flow_entry_count(child)
      call_handler('%{name}_flow_entry_count', child) { yield }
    end

    def rewrite_child(child)
      call_handler('rewrite_%{name}', child) { yield }
    end

    def validate_children_types(nodes)
      mismatches = self.class.check_children_types(nodes)
      unless mismatches.empty?
        raise TypeError, "Invalid children types for #{self.class}(type: #{self.type}): #{mismatches}"
      end
    end

    module ClassMethods
      def has_child(flow_entry_count: nil, rewrite: nil, remap: nil, rest: false, **h)
        raise "Only one custom named argument" if h.size > 1
        name, types = h.first
        raise "Need a name (symbol)" unless name.is_a?(Symbol)
        if types.is_a? Hash
          type_map = types
          raise "Use either remap or a hash as type but not both" if remap
          remap = -> (child) { type_map[child.type] }
          types = type_map.values
        end
        update_children_const(name, rest: rest)
        define_accessor(name)
        add_runtime_check(name, types)
        define_handler(:"#{name}_flow_entry_count", flow_entry_count)
        define_handler(:"rewrite_#{name}", rewrite)
        define_handler(:"remap_#{name}", remap)
        self
      end

      def has_extra_children(**h)
        has_child(rest: true, **h)
      end

      def child_index_to_name(index, nb_children)
        self::CHILDREN.each do |name, i|
          return name if i == index || (i == index - nb_children) ||
            (i.is_a?(Range) && i.begin <= index && i.end + nb_children >= index)
        end
        raise IndexError, "index #{index} does not correspond to any child of #{self}"
      end

      def check_children_types(nodes)
        types = expected_types(nodes)
        nodes_mismatches(nodes, types)
      end

      # Returns a subclass or the base Node, according to type
      def factory(type, index)
        class_name = type.capitalize
        Node.const_defined?(class_name) ? Node.const_get(class_name) : Node
      end

      # Augment creates a covered node from the child_base_node.
      def augment(child_base_node, covered_code, parent, child_index = 0)
        # Skip children that aren't node themselves (e.g. the `method` child of a :def node)
        return child_base_node unless child_base_node.is_a? Parser::AST::Node
        child_name = self.child_index_to_name(child_index, parent.children.size)
        klass = parent.call_handler('remap_%{name}', child_base_node, child_name) {
          factory(child_base_node.type, child_index)
        }
        klass.new(child_base_node, covered_code, parent, child_index)
      end

      private

      def expected_types(nodes)
        types = self::CHILDREN.flat_map do |name, i|
          type = self::CHILDREN_TYPES[name]
          Array.new(nodes.values_at(i).size, type)
        end
      end

      def nodes_mismatches(nodes, types)
        nodes = nodes.dup
        nodes[nodes.size...types.size] = nil
        nodes.zip(types).reject do |node, type|
          node_matches_type?(node, type)
        end
      end

      def node_matches_type?(node, expected)
        case expected
        when :any
          true
{'fraction_non_alphanumeric': 0.09398730237769203, 'fraction_numerical': 0.0007469189592929168, 'mean_word_length': 2.578619153674833, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 9, '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': '3683007', 'n_tokens_mistral': 2483, 'n_tokens_neox': 2450, 'n_words': 633}