<commit_before>module Cucumber
  module Core
    module Test
      class TagFilter
        include Cucumber.initializer(:filter_expressions, :receiver)

        def test_case(test_case)
          tag_counter.count(test_case)
          if test_case.match_tags?(filter_expressions)
            test_case.describe_to(receiver)
          end
          self
        end

        def done
          tag_limits.enforce(tag_counter)
          @receiver.done
          self
        end

        private
        def tag_counter
          @tag_counter ||= TagCounter.new
        end

        def tag_limits
          @tag_limits ||= TagLimits.new(filter_expressions)
        end

        class TagCounter
          attr_reader :tag_name_locations, :tag_name_counts
          private :tag_name_locations, :tag_name_counts
          def initialize
            @tag_name_locations = Hash.new { [] }
            @tag_name_counts = Hash.new { 0 }
          end

          def count(test_case)
            test_case.tags.each do |tag|
              tag_name_locations[tag.name] += [test_case.location]
              tag_name_counts[tag.name] += 1
            end
            self
          end

          def count_for(tag_name)
            tag_name_counts[tag_name]
          end

          def locations_for(tag_name)
            tag_name_locations[tag_name]
          end
        end

        class TagLimits
          TAG_MATCHER = /^
            (?:~)?                 #The tag negation symbol "~". This is optional and not captured.
            (?<tag_name>\@[\w\d]+) #Captures the tag name including the "@" symbol.
            \:                     #The seperator, ":", between the tag name and the limit.
            (?<limit>\d+)          #Caputres the limit number.
          $/x

          attr_reader :limit_list
          private :limit_list
          def initialize(filter_expressions)
            @limit_list = Array(filter_expressions).flat_map do |raw_expression|
              raw_expression.split(/\s*,\s*/)
            end.map do |filter_expression|
              TAG_MATCHER.match(filter_expression)
            end.compact.reduce({}) do |limit_list, matchdata|
              limit_list[matchdata[:tag_name]] = Integer(matchdata[:limit])
              limit_list
            end
          end

          def enforce(tag_counter)
            limit_breaches = limit_list.reduce([]) do |breaches, (tag_name, limit)|
              tag_count = tag_counter.count_for(tag_name)
              if tag_count > limit
                tag_locations = tag_counter.locations_for(tag_name)
                breaches << TagLimitBreach.new(
                  tag_count,
                  limit,<commit_msg>Rename `TagCounter` to `TestCases`<commit_after>module Cucumber
  module Core
    module Test
      class TagFilter
        include Cucumber.initializer(:filter_expressions, :receiver)

        def test_case(test_case)
          test_cases << test_case
          if test_case.match_tags?(filter_expressions)
            test_case.describe_to(receiver)
          end
          self
        end

        def done
          tag_limits.enforce(test_cases)
          receiver.done
          self
        end

        private
        def test_cases
          @test_cases ||= TestCases.new
        end

        def tag_limits
          @tag_limits ||= TagLimits.new(filter_expressions)
        end

        class TestCases
          attr_reader :test_cases_by_tag_name
          private :test_cases_by_tag_name
          def initialize
            @test_cases_by_tag_name = Hash.new { [] }
          end

          def <<(test_case)
            test_case.tags.each do |tag|
              test_cases_by_tag_name[tag.name] += [test_case]
            end
            self
          end

          def with_tag_name(tag_name)
            test_cases_by_tag_name[tag_name]
          end
        end

        class TagLimits
          TAG_MATCHER = /^
            (?:~)?                 #The tag negation symbol "~". This is optional and not captured.
            (?<tag_name>\@[\w\d]+) #Captures the tag name including the "@" symbol.
            \:                     #The seperator, ":", between the tag name and the limit.
            (?<limit>\d+)          #Caputres the limit number.
          $/x

          attr_reader :limit_list
          private :limit_list
          def initialize(filter_expressions)
            @limit_list = Array(filter_expressions).flat_map do |raw_expression|
              raw_expression.split(/\s*,\s*/)
            end.map do |filter_expression|
              TAG_MATCHER.match(filter_expression)
            end.compact.reduce({}) do |limit_list, matchdata|
              limit_list[matchdata[:tag_name]] = Integer(matchdata[:limit])
              limit_list
            end
          end

          def enforce(test_cases)
            limit_breaches = limit_list.reduce([]) do |breaches, (tag_name, limit)|
              tag_count = test_cases.with_tag_name(tag_name).count
              if tag_count > limit
                tag_locations = test_cases.with_tag_name(tag_name).map(&:location)
                breaches << TagLimitBreach.new(
                  tag_count,
                  limit,
{'fraction_non_alphanumeric': 0.07922501438710915, 'fraction_numerical': 0.0003836562440053712, 'mean_word_length': 1.6096096096096095, 'pattern_counts': {'":': 2, '<': 15, '<?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': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4643288', 'n_tokens_mistral': 1494, 'n_tokens_neox': 1431, 'n_words': 298}