<commit_before># frozen_string_literal: true

module Capybara
  module Queries
    class SelectorQuery < Queries::BaseQuery
      attr_reader :expression, :selector, :locator, :options
      VALID_KEYS = COUNT_KEYS + %i[text id class style visible exact exact_text normalize_ws match wait filter_set]
      VALID_MATCH = %i[first smart prefer_exact one].freeze

      def initialize(*args,
                     session_options:,
                     enable_aria_label: session_options.enable_aria_label,
                     test_id: session_options.test_id,
                     **options,
                     &filter_block)
        @resolved_node = nil
        @resolved_count = 0
        @options = options.dup
        super(@options)
        self.session_options = session_options

        @selector = find_selector(args[0].is_a?(Symbol) ? args.shift : args[0])
        @locator = args.shift
        @filter_block = filter_block

        raise ArgumentError, "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?

        selector_config = { enable_aria_label: enable_aria_label, test_id: test_id }
        @expression = selector.call(@locator, @options.merge(selector_config: selector_config))

        warn_exact_usage

        assert_valid_keys
      end

      def name; selector.name; end
      def label; selector.label || selector.name; end

      def description(only_applied = false)
        desc = +''
        show_for = show_for_stage(only_applied)

        if show_for[:any]
          desc << 'visible ' if visible == :visible
          desc << 'non-visible ' if visible == :hidden
        end
        desc << "#{label} #{locator.inspect}"
        if show_for[:any]
          desc << " with#{' exact' if exact_text == true} text #{options[:text].inspect}" if options[:text]
          desc << " with exact text #{exact_text}" if exact_text.is_a?(String)
        end
        desc << " with id #{options[:id]}" if options[:id]
        desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
        desc << case options[:style]
        when String
          " with style attribute #{options[:style].inspect}"
        when Regexp
          " with style attribute matching #{options[:style].inspect}"
        when Hash
          " with styles #{options[:style].inspect}"
        else ''
        end
        desc << selector.description(node_filters: show_for[:node], **options)
        desc << ' that also matches the custom filter block' if @filter_block && show_for[:node]
        desc << " within #{@resolved_node.inspect}" if describe_within?
        if locator.is_a?(String) && locator.start_with?('#', './/', '//')
          unless selector.raw_locator?
            desc << "\nNote: It appears you may be passing a CSS selector or XPath expression rather than a locator. " \
                    "Please see the documentation for acceptable locator values.\n\n"
          end
        end
        desc
      end

      def applied_description
        description(true)
      end

      def matches_filters?(node, node_filter_errors = [])
        return true if (@resolved_node&.== node) && options[:allow_self]

        matches_locator_filter?(node) &&
          matches_system_filters?(node) &&
          matches_node_filters?(node, node_filter_errors) &&
          matches_filter_block?(node)
      rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : []) # rubocop:disable Naming/RescuedExceptionsVariableName
        false
      end

      def visible
        case (vis = options.fetch(:visible) { default_visibility })
        when true then :visible
        when false then :all
        else vis
        end
      end

      def exact?
        supports_exact? ? options.fetch(:exact, session_options.exact) : false
      end

      def match
        options.fetch(:match, session_options.match)
      end

      def xpath(exact = nil)
        exact = exact? if exact.nil?
        expr = apply_expression_filters(@expression)
        expr = exact ? expr.to_xpath(:exact) : expr.to_s if expr.respond_to?(:to_xpath)
        expr = filtered_expression(expr)
        expr = "(#{expr})[#{xpath_text_conditions}]" if try_text_match_in_expression?
        expr
      end

      def css
        filtered_expression(apply_expression_filters(@expression))
      end

      # @api private
      def resolve_for(node, exact = nil)
        applied_filters.clear
        @resolved_node = node
        @resolved_count += 1
        node.synchronize do
          children = find_nodes_by_selector_format(node, exact).map(&method(:to_element))
          Capybara::Result.new(children, self)
        end
      end

      # @api private
      def supports_exact?
        @expression.respond_to? :to_xpath
      end

      def failure_message
        +"expected to find #{applied_description}" << count_message
      end

      def negative_failure_message
        +"expected not to find #{applied_description}" << count_message
      end

    private

      def text_fragments
        text = (options[:text] || options[:exact_text])
        text.is_a?(String) ? text.split : []
      end

      def xpath_text_conditions
        (options[:text] || options[:exact_text]).split.map { |txt| XPath.contains(txt) }.reduce(&:&)
      end

      def try_text_match_in_expression?
        first_try? &&
          (options[:text] || options[:exact_text]).is_a?(String) &&
          @resolved_node&.respond_to?(:session) &&
          @resolved_node.session.driver.wait?
      end

      def first_try?
        @resolved_count == 1
      end

      def show_for_stage(only_applied)
        lambda do |stage = :any|
          !only_applied || (stage == :any ? applied_filters.any? : applied_filters.include?(stage))
        end
      end

      def applied_filters
        @applied_filters ||= []
      end

      def find_selector(locator)
        case locator
        when Symbol then Selector[locator]
        else Selector.for(locator)
        end || Selector[session_options.default_selector]
      end
<commit_msg>Use Regexp text options in initial XPath queries<commit_after># frozen_string_literal: true

module Capybara
  module Queries
    class SelectorQuery < Queries::BaseQuery
      attr_reader :expression, :selector, :locator, :options
      VALID_KEYS = COUNT_KEYS + %i[text id class style visible exact exact_text normalize_ws match wait filter_set]
      VALID_MATCH = %i[first smart prefer_exact one].freeze

      def initialize(*args,
                     session_options:,
                     enable_aria_label: session_options.enable_aria_label,
                     test_id: session_options.test_id,
                     **options,
                     &filter_block)
        @resolved_node = nil
        @resolved_count = 0
        @options = options.dup
        super(@options)
        self.session_options = session_options

        @selector = find_selector(args[0].is_a?(Symbol) ? args.shift : args[0])
        @locator = args.shift
        @filter_block = filter_block

        raise ArgumentError, "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?

        selector_config = { enable_aria_label: enable_aria_label, test_id: test_id }
        @expression = selector.call(@locator, @options.merge(selector_config: selector_config))

        warn_exact_usage

        assert_valid_keys
      end

      def name; selector.name; end
      def label; selector.label || selector.name; end

      def description(only_applied = false)
        desc = +''
        show_for = show_for_stage(only_applied)

        if show_for[:any]
          desc << 'visible ' if visible == :visible
          desc << 'non-visible ' if visible == :hidden
        end
        desc << "#{label} #{locator.inspect}"
        if show_for[:any]
          desc << " with#{' exact' if exact_text == true} text #{options[:text].inspect}" if options[:text]
          desc << " with exact text #{exact_text}" if exact_text.is_a?(String)
        end
        desc << " with id #{options[:id]}" if options[:id]
        desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
        desc << case options[:style]
        when String
          " with style attribute #{options[:style].inspect}"
        when Regexp
          " with style attribute matching #{options[:style].inspect}"
        when Hash
          " with styles #{options[:style].inspect}"
        else ''
        end
        desc << selector.description(node_filters: show_for[:node], **options)
        desc << ' that also matches the custom filter block' if @filter_block && show_for[:node]
        desc << " within #{@resolved_node.inspect}" if describe_within?
        if locator.is_a?(String) && locator.start_with?('#', './/', '//')
          unless selector.raw_locator?
            desc << "\nNote: It appears you may be passing a CSS selector or XPath expression rather than a locator. " \
                    "Please see the documentation for acceptable locator values.\n\n"
          end
        end
        desc
      end

      def applied_description
        description(true)
      end

      def matches_filters?(node, node_filter_errors = [])
        return true if (@resolved_node&.== node) && options[:allow_self]

        matches_locator_filter?(node) &&
          matches_system_filters?(node) &&
          matches_node_filters?(node, node_filter_errors) &&
          matches_filter_block?(node)
      rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : []) # rubocop:disable Naming/RescuedExceptionsVariableName
        false
      end

      def visible
        case (vis = options.fetch(:visible) { default_visibility })
        when true then :visible
        when false then :all
        else vis
        end
      end

      def exact?
        supports_exact? ? options.fetch(:exact, session_options.exact) : false
      end

      def match
        options.fetch(:match, session_options.match)
      end

      def xpath(exact = nil)
        exact = exact? if exact.nil?
        expr = apply_expression_filters(@expression)
        expr = exact ? expr.to_xpath(:exact) : expr.to_s if expr.respond_to?(:to_xpath)
        expr = filtered_expression(expr)
        expr = "(#{expr})[#{xpath_text_conditions}]" if try_text_match_in_expression?
        expr
      end

      def css
        filtered_expression(apply_expression_filters(@expression))
      end

      # @api private
      def resolve_for(node, exact = nil)
        applied_filters.clear
        @resolved_node = node
        @resolved_count += 1
        node.synchronize do
          children = find_nodes_by_selector_format(node, exact).map(&method(:to_element))
          Capybara::Result.new(children, self)
        end
      end

      # @api private
      def supports_exact?
        @expression.respond_to? :to_xpath
      end

      def failure_message
        +"expected to find #{applied_description}" << count_message
      end

      def negative_failure_message
        +"expected not to find #{applied_description}" << count_message
      end

    private

      def text_fragments
        text = (options[:text] || options[:exact_text])
        text.is_a?(String) ? text.split : []
      end

      def xpath_text_conditions
        text = (options[:text] || options[:exact_text])
        case text
        when String
          text.split.map { |txt| XPath.contains(txt) }.reduce(&:&)
        when Regexp
          condition = XPath.current
          condition = condition.uppercase if text.casefold?
          Selector::RegexpDisassembler.new(text).alternated_substrings.map do |strs|
            strs.flat_map(&:split).map { |str| condition.contains(str) }.reduce(:&)
          end.reduce(:|)
        end
      end

      def try_text_match_in_expression?
        first_try? &&
          (options[:text] || options[:exact_text]) &&
          @resolved_node&.respond_to?(:session) &&
          @resolved_node.session.driver.wait?
      end

      def first_try?
        @resolved_count == 1
      end

      def show_for_stage(only_applied)
        lambda do |stage = :any|
          !only_applied || (stage == :any ? applied_filters.any? : applied_filters.include?(stage))
        end
      end

      def applied_filters
        @applied_filters ||= []
      end

      def find_selector(locator)
        case locator
        when Symbol then Selector[locator]
        else Selector.for(locator)
        end || Selector[session_options.default_selector]
      end

{'fraction_non_alphanumeric': 0.10390333094840608, 'fraction_numerical': 0.0007949757532395262, 'mean_word_length': 2.3448550917309228, 'pattern_counts': {'":': 0, '<': 61, '<?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': '5309985', 'n_tokens_mistral': 3600, 'n_tokens_neox': 3492, 'n_words': 846}