<commit_before>module Schemacop
  module V3
    class HashNode < Node
      ATTRIBUTES = %i[
        type
        min_properties
        max_properties
        dependencies
        property_names
      ].freeze

      supports_children(name: true)

      attr_reader :properties

      def self.allowed_options
        super + ATTRIBUTES - %i[dependencies] + %i[additional_properties]
      end

      def self.dsl_methods
        super + NodeRegistry.dsl_methods(true) + %i[dsl_dep dsl_add]
      end

      def add_child(node)
        unless node.name
          fail Exceptions::InvalidSchemaError, 'Child nodes must have a name.'
        end

        @properties[node.name] = node
      end

      def dsl_add(type, **options, &block)
        if @options[:additional_properties].is_a?(Node)
          fail Exceptions::InvalidSchemaError, 'You can only use "add" once to specify additional properties.'
        end

        @options[:additional_properties] = create(type, **options, &block)
      end

      def dsl_dep(source, *targets, **_kwargs)
        @options[:dependencies] ||= {}
        @options[:dependencies][source] = targets
      end

      def as_json
        properties = {}
        pattern_properties = {}

        @properties.each do |name, property|
          if name.is_a?(Regexp)
            pattern_properties[name] = property
          else
            properties[name] = property
          end
        end

        json = {}
        json[:properties] = Hash[properties.values.map { |p| [p.name, p.as_json] }] if properties.any?
        json[:patternProperties] = Hash[pattern_properties.values.map { |p| [sanitize_exp(p.name), p.as_json] }] if pattern_properties.any?

        # In schemacop, by default, additional properties are not allowed,
        # the users explicitly need to enable additional properties
        if options[:additional_properties] == true
          json[:additionalProperties] = true
        elsif options[:additional_properties].is_a?(Node)
          json[:additionalProperties] = options[:additional_properties].as_json
        else
          json[:additionalProperties] = false
        end

        required_properties = @properties.values.select(&:required?).map(&:name)

        if required_properties.any?
          json[:required] = required_properties
        end

        return process_json(ATTRIBUTES, json)
      end

      def sanitize_exp(exp)
        exp = exp.to_s
        if exp.start_with?('(?-mix:')
          exp = exp.to_s.gsub(/^\(\?-mix:/, '').gsub(/\)$/, '')
        end
        return exp
      end

      def allowed_types
        { Hash => :object }
      end

      def _validate(data, result: Result.new)
        super_data = super
        return if super_data.nil?

        original_data_hash = super_data.dup
        data_hash = super_data.with_indifferent_access

        if original_data_hash.size != data_hash.size
          ambiguous_properties = original_data_hash.keys - data_hash.keys

          result.error "Has #{ambiguous_properties.size} ambiguous properties: #{ambiguous_properties}."
        end

        # Validate min_properties #
        if options[:min_properties] && data_hash.size < options[:min_properties]
          result.error "Has #{data_hash.size} properties but needs at least #{options[:min_properties]}."
        end

        # Validate max_properties #
        if options[:max_properties] && data_hash.size > options[:max_properties]
          result.error "Has #{data_hash.size} properties but needs at most #{options[:max_properties]}."
        end

        # Validate specified properties #
        @properties.each_value do |node|<commit_msg>Make 'sanitize_exp` a class method so it can be accessed from the outside<commit_after>module Schemacop
  module V3
    class HashNode < Node
      ATTRIBUTES = %i[
        type
        min_properties
        max_properties
        dependencies
        property_names
      ].freeze

      supports_children(name: true)

      attr_reader :properties

      def self.allowed_options
        super + ATTRIBUTES - %i[dependencies] + %i[additional_properties]
      end

      def self.dsl_methods
        super + NodeRegistry.dsl_methods(true) + %i[dsl_dep dsl_add]
      end

      def self.sanitize_exp(exp)
        exp = exp.to_s
        if exp.start_with?('(?-mix:')
          exp = exp.to_s.gsub(/^\(\?-mix:/, '').gsub(/\)$/, '')
        end
        return exp
      end

      def add_child(node)
        unless node.name
          fail Exceptions::InvalidSchemaError, 'Child nodes must have a name.'
        end

        @properties[node.name] = node
      end

      def dsl_add(type, **options, &block)
        if @options[:additional_properties].is_a?(Node)
          fail Exceptions::InvalidSchemaError, 'You can only use "add" once to specify additional properties.'
        end

        @options[:additional_properties] = create(type, **options, &block)
      end

      def dsl_dep(source, *targets, **_kwargs)
        @options[:dependencies] ||= {}
        @options[:dependencies][source] = targets
      end

      def as_json
        properties = {}
        pattern_properties = {}

        @properties.each do |name, property|
          if name.is_a?(Regexp)
            pattern_properties[name] = property
          else
            properties[name] = property
          end
        end

        json = {}
        json[:properties] = Hash[properties.values.map { |p| [p.name, p.as_json] }] if properties.any?
        json[:patternProperties] = Hash[pattern_properties.values.map { |p| [self.class.sanitize_exp(p.name), p.as_json] }] if pattern_properties.any?

        # In schemacop, by default, additional properties are not allowed,
        # the users explicitly need to enable additional properties
        if options[:additional_properties] == true
          json[:additionalProperties] = true
        elsif options[:additional_properties].is_a?(Node)
          json[:additionalProperties] = options[:additional_properties].as_json
        else
          json[:additionalProperties] = false
        end

        required_properties = @properties.values.select(&:required?).map(&:name)

        if required_properties.any?
          json[:required] = required_properties
        end

        return process_json(ATTRIBUTES, json)
      end

      def allowed_types
        { Hash => :object }
      end

      def _validate(data, result: Result.new)
        super_data = super
        return if super_data.nil?

        original_data_hash = super_data.dup
        data_hash = super_data.with_indifferent_access

        if original_data_hash.size != data_hash.size
          ambiguous_properties = original_data_hash.keys - data_hash.keys

          result.error "Has #{ambiguous_properties.size} ambiguous properties: #{ambiguous_properties}."
        end

        # Validate min_properties #
        if options[:min_properties] && data_hash.size < options[:min_properties]
          result.error "Has #{data_hash.size} properties but needs at least #{options[:min_properties]}."
        end

        # Validate max_properties #
        if options[:max_properties] && data_hash.size > options[:max_properties]
          result.error "Has #{data_hash.size} properties but needs at most #{options[:max_properties]}."
        end

        # Validate specified properties #
        @properties.each_value do |node|
{'fraction_non_alphanumeric': 0.09837618403247632, 'fraction_numerical': 0.00027063599458728013, 'mean_word_length': 2.3872593950504126, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4599894', 'n_tokens_mistral': 2076, 'n_tokens_neox': 2010, 'n_words': 483}