From 66689fd3526d19393c374c68b8c40470873520c0 Mon Sep 17 00:00:00 2001 From: Thomas Walpole Date: Thu, 8 Nov 2018 11:04:31 -0800 Subject: [PATCH] Code refactor --- lib/capybara/selector/regexp_disassembler.rb | 51 +++++++++++--------- spec/regexp_dissassembler_spec.rb | 14 +++++- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/lib/capybara/selector/regexp_disassembler.rb b/lib/capybara/selector/regexp_disassembler.rb index 9b24c99b..9ebaead4 100644 --- a/lib/capybara/selector/regexp_disassembler.rb +++ b/lib/capybara/selector/regexp_disassembler.rb @@ -28,6 +28,10 @@ module Capybara private def remove_and_covered(strings) + # delete_if is documented to modify the array after every block iteration - this doesn't appear to be true + # uniq the strings to prevent identical strings from removing each other + strings.uniq! + # If we have "ab" and "abcd" required - only need to check for "abcd" strings.delete_if do |sub_string| strings.any? do |cover_string| @@ -65,8 +69,12 @@ module Capybara exp.quantifier&.min || 1 end + def max_repeat(exp) + exp.quantifier&.max || 1 + end + def fixed_repeat?(exp) - min_repeat(exp) == (exp.quantifier&.max || 1) + min_repeat(exp) == max_repeat(exp) end def optional?(exp) @@ -97,9 +105,10 @@ module Capybara end end - def extract_strings(expression, strings = [], alternation: false) + def extract_strings(expression, alternation: false) + strings = [] expression.each do |exp| # rubocop:disable Metrics/BlockLength - if optional?(exp) && !(alternation && zero_or_one?(exp)) + if optional?(exp) && !alternation strings.push(nil) next end @@ -115,39 +124,33 @@ module Capybara end if exp.terminal? - case exp.type - when :literal - if zero_or_one?(exp) - strings.push(Set.new([[''], [exp.text]])) - next - else - strings.push(exp.text * min_repeat(exp)) - end - when :escape - if zero_or_one?(exp) - strings.push(Set.new([[''], [exp.text]])) - next - else - strings.push(exp.char * min_repeat(exp)) - end + text = case exp.type + when :literal then exp.text + when :escape then exp.char else strings.push(nil) + next end - elsif alternation && zero_or_one?(exp) + + if optional?(exp) + strings.push(Set.new([[''], [text]])) + strings.push(nil) unless max_repeat(exp) == 1 + next + else + strings.push(text * min_repeat(exp)) + end + elsif optional?(exp) strings.push(Set.new([[''], extract_strings(exp, alternation: true)])) + strings.push(nil) unless max_repeat(exp) == 1 next else - min_repeat(exp).times { extract_strings(exp, strings, alternation: alternation) } + min_repeat(exp).times { strings.concat extract_strings(exp, alternation: alternation) } end strings.push(nil) unless fixed_repeat?(exp) end strings end - def zero_or_one?(exp) - exp.quantity == [0, 1] - end - def alternative_strings(expression) alternatives = expression.alternatives.map { |sub_exp| extract_strings(sub_exp, alternation: true) } if alternatives.all?(&:any?) diff --git a/spec/regexp_dissassembler_spec.rb b/spec/regexp_dissassembler_spec.rb index 542555f4..a0ab55d8 100644 --- a/spec/regexp_dissassembler_spec.rb +++ b/spec/regexp_dissassembler_spec.rb @@ -124,7 +124,12 @@ RSpec.describe Capybara::Selector::RegexpDisassembler do /cd(ef|gh)?ij/ => %w[cd ij], /cd(ef|gh)+ij/ => %w[cd ij], /cd(ef|gh){2}ij/ => %w[cd ij], - /(cd(ef|g*))/ => %w[cd] + /(cd(ef|g*))/ => %w[cd], + /ab(cd){0,2}ef/ => %w[ab ef], + /ab(cd){0,1}ef/ => %w[ab ef], + /ab(cd|cd)ef/ => %w[ab ef], + /ab(cd|cd)?ef/ => %w[ab ef], + /ab\\?cd/ => %w[ab cd] }.each do |regexp, expected| expect(Capybara::Selector::RegexpDisassembler.new(regexp).substrings).to eq expected end @@ -154,7 +159,12 @@ RSpec.describe Capybara::Selector::RegexpDisassembler do /(abc)?(d|e)/ => [%w[d], %w[e]], /(abc*de)?(d|e)/ => [%w[d], %w[e]], /(abc*de)?(d|e?)/ => [], - /(abc)?(d|e?)/ => [] + /(abc)?(d|e?)/ => [], + /ab(cd){0,2}ef/ => [%w[ab ef]], + /ab(cd){0,1}ef/ => [%w[abef], %w[abcdef]], + /ab(cd|cd)ef/ => [%w[abcdef]], + /ab(cd|cd)?ef/ => [%w[abef], %w[abcdef]], + /ab\\?cd/ => [%w[abcd], %w[ab\cd]] ) end