From e5f54465284b4505042fca10ace998e1d29c2313 Mon Sep 17 00:00:00 2001 From: Yuki Nishijima Date: Fri, 22 May 2020 17:17:10 -0400 Subject: [PATCH] Sync did_you_mean --- lib/did_you_mean.rb | 4 +- .../experimental/ivar_name_correction.rb | 39 ++-- .../spell_checkers/method_name_checker.rb | 7 +- .../spell_checkers/require_path_checker.rb | 35 +++ lib/did_you_mean/tree_spell_checker.rb | 166 ++++++-------- .../spell_checking/test_method_name_check.rb | 7 + .../spell_checking/test_require_path_check.rb | 32 +++ test/did_you_mean/test_tree_spell_checker.rb | 205 +++++++++--------- test/did_you_mean/test_verbose_formatter.rb | 1 + test/did_you_mean/tree_spell/human_typo.rb | 34 +-- 10 files changed, 275 insertions(+), 255 deletions(-) create mode 100644 lib/did_you_mean/spell_checkers/require_path_checker.rb create mode 100644 test/did_you_mean/spell_checking/test_require_path_check.rb diff --git a/lib/did_you_mean.rb b/lib/did_you_mean.rb index b8f92579ca..ab7e6b01a8 100644 --- a/lib/did_you_mean.rb +++ b/lib/did_you_mean.rb @@ -6,6 +6,7 @@ require_relative 'did_you_mean/spell_checkers/name_error_checkers' require_relative 'did_you_mean/spell_checkers/method_name_checker' require_relative 'did_you_mean/spell_checkers/key_error_checker' require_relative 'did_you_mean/spell_checkers/null_checker' +require_relative 'did_you_mean/spell_checkers/require_path_checker' require_relative 'did_you_mean/formatters/plain_formatter' require_relative 'did_you_mean/tree_spell_checker' @@ -95,8 +96,9 @@ module DidYouMean correct_error NameError, NameErrorCheckers correct_error KeyError, KeyErrorChecker correct_error NoMethodError, MethodNameChecker + correct_error LoadError, RequirePathChecker if RUBY_VERSION >= '2.8.0' - # Returns the currenctly set formatter. By default, it is set to +DidYouMean::Formatter+. + # Returns the currently set formatter. By default, it is set to +DidYouMean::Formatter+. def self.formatter @@formatter end diff --git a/lib/did_you_mean/experimental/ivar_name_correction.rb b/lib/did_you_mean/experimental/ivar_name_correction.rb index 322e422c6b..7b97ff4fc0 100644 --- a/lib/did_you_mean/experimental/ivar_name_correction.rb +++ b/lib/did_you_mean/experimental/ivar_name_correction.rb @@ -1,22 +1,11 @@ # frozen-string-literal: true -require_relative '../../did_you_mean' +require_relative '../../did_you_mean/spell_checker' +require_relative '../../did_you_mean/spell_checkers/method_name_checker' module DidYouMean module Experimental #:nodoc: - class IvarNameCheckerBuilder #:nodoc: - attr_reader :original_checker - - def initialize(original_checker) #:nodoc: - @original_checker = original_checker - end - - def new(no_method_error) #:nodoc: - IvarNameChecker.new(no_method_error, original_checker: @original_checker) - end - end - - class IvarNameChecker #:nodoc: + class IvarNameChecker < ::DidYouMean::MethodNameChecker #:nodoc: REPLS = { "(irb)" => -> { Readline::HISTORY.to_a.last } } @@ -29,10 +18,10 @@ module DidYouMean end end - attr_reader :original_checker + attr_reader :location, :ivar_names - def initialize(no_method_error, original_checker: ) - @original_checker = original_checker.new(no_method_error) + def initialize(no_method_error) + super(no_method_error) @location = no_method_error.backtrace_locations.first @ivar_names = no_method_error.frame_binding.receiver.instance_variables @@ -41,22 +30,22 @@ module DidYouMean end def corrections - original_checker.corrections + ivar_name_corrections + super + ivar_name_corrections end def ivar_name_corrections - @ivar_name_corrections ||= SpellChecker.new(dictionary: @ivar_names).correct(receiver_name.to_s) + @ivar_name_corrections ||= SpellChecker.new(dictionary: ivar_names).correct(receiver_name.to_s) end private def receiver_name - return unless @original_checker.receiver.nil? + return unless receiver.nil? - abs_path = @location.absolute_path - lineno = @location.lineno + abs_path = location.absolute_path + lineno = location.lineno - /@(\w+)*\.#{@original_checker.method_name}/ =~ line(abs_path, lineno).to_s && $1 + /@(\w+)*\.#{method_name}/ =~ line(abs_path, lineno).to_s && $1 end def line(abs_path, lineno) @@ -71,6 +60,6 @@ module DidYouMean end end - NameError.send(:attr, :frame_binding) - SPELL_CHECKERS['NoMethodError'] = Experimental::IvarNameCheckerBuilder.new(SPELL_CHECKERS['NoMethodError']) + NoMethodError.send(:attr, :frame_binding) + SPELL_CHECKERS['NoMethodError'] = Experimental::IvarNameChecker end diff --git a/lib/did_you_mean/spell_checkers/method_name_checker.rb b/lib/did_you_mean/spell_checkers/method_name_checker.rb index 3a38245f0c..0483127d6f 100644 --- a/lib/did_you_mean/spell_checkers/method_name_checker.rb +++ b/lib/did_you_mean/spell_checkers/method_name_checker.rb @@ -43,7 +43,12 @@ module DidYouMean end def corrections - @corrections ||= SpellChecker.new(dictionary: RB_RESERVED_WORDS + method_names).correct(method_name) - names_to_exclude + @corrections ||= begin + dictionary = method_names + dictionary = RB_RESERVED_WORDS + dictionary if @private_call + + SpellChecker.new(dictionary: dictionary).correct(method_name) - names_to_exclude + end end def method_names diff --git a/lib/did_you_mean/spell_checkers/require_path_checker.rb b/lib/did_you_mean/spell_checkers/require_path_checker.rb new file mode 100644 index 0000000000..aaf877b412 --- /dev/null +++ b/lib/did_you_mean/spell_checkers/require_path_checker.rb @@ -0,0 +1,35 @@ +# frozen-string-literal: true + +require_relative "../spell_checker" +require_relative "../tree_spell_checker" + +module DidYouMean + class RequirePathChecker + attr_reader :path + + INITIAL_LOAD_PATH = $LOAD_PATH.dup.freeze + ENV_SPECIFIC_EXT = ".#{RbConfig::CONFIG["DLEXT"]}" + + private_constant :INITIAL_LOAD_PATH, :ENV_SPECIFIC_EXT + + def self.requireables + @requireables ||= INITIAL_LOAD_PATH + .flat_map {|path| Dir.glob("**/???*{.rb,#{ENV_SPECIFIC_EXT}}", base: path) } + .map {|path| path.chomp!(".rb") || path.chomp!(ENV_SPECIFIC_EXT) } + end + + def initialize(exception) + @path = exception.path + end + + def corrections + @corrections ||= begin + threshold = path.size * 2 + dictionary = self.class.requireables.reject {|str| str.size >= threshold } + spell_checker = path.include?("/") ? TreeSpellChecker : SpellChecker + + spell_checker.new(dictionary: dictionary).correct(path).uniq + end + end + end +end diff --git a/lib/did_you_mean/tree_spell_checker.rb b/lib/did_you_mean/tree_spell_checker.rb index 6a5b485413..799f07fcf0 100644 --- a/lib/did_you_mean/tree_spell_checker.rb +++ b/lib/did_you_mean/tree_spell_checker.rb @@ -1,137 +1,109 @@ +# frozen_string_literal: true + module DidYouMean # spell checker for a dictionary that has a tree # structure, see doc/tree_spell_checker_api.md class TreeSpellChecker - attr_reader :dictionary, :dimensions, :separator, :augment + attr_reader :dictionary, :separator, :augment def initialize(dictionary:, separator: '/', augment: nil) @dictionary = dictionary @separator = separator @augment = augment - @dimensions = parse_dimensions end def correct(input) - plausibles = plausible_dimensions input - return no_idea(input) if plausibles.empty? - suggestions = find_suggestions input, plausibles - return no_idea(input) if suggestions.empty? + plausibles = plausible_dimensions(input) + return fall_back_to_normal_spell_check(input) if plausibles.empty? + + suggestions = find_suggestions(input, plausibles) + return fall_back_to_normal_spell_check(input) if suggestions.empty? + suggestions end + def dictionary_without_leaves + @dictionary_without_leaves ||= dictionary.map { |word| word.split(separator)[0..-2] }.uniq + end + + def tree_depth + @tree_depth ||= dictionary_without_leaves.max { |a, b| a.size <=> b.size }.size + end + + def dimensions + @dimensions ||= tree_depth.times.map do |index| + dictionary_without_leaves.map { |element| element[index] }.compact.uniq + end + end + + def find_leaves(path) + path_with_separator = "#{path}#{separator}" + + dictionary + .select {|str| str.include?(path_with_separator) } + .map {|str| str.gsub(path_with_separator, '') } + end + + def plausible_dimensions(input) + input.split(separator)[0..-2] + .map + .with_index { |element, index| correct_element(dimensions[index], element) if dimensions[index] } + .compact + end + + def possible_paths(states) + states.map { |state| state.join(separator) } + end + private - def parse_dimensions - ParseDimensions.new(dictionary, separator).call - end - def find_suggestions(input, plausibles) states = plausibles[0].product(*plausibles[1..-1]) - paths = possible_paths states - leaf = input.split(separator).last - ideas = find_ideas(paths, leaf) - ideas.compact.flatten + paths = possible_paths(states) + leaf = input.split(separator).last + + find_ideas(paths, leaf) end - def no_idea(input) + def fall_back_to_normal_spell_check(input) return [] unless augment + ::DidYouMean::SpellChecker.new(dictionary: dictionary).correct(input) end def find_ideas(paths, leaf) - paths.map do |path| + paths.flat_map do |path| names = find_leaves(path) - ideas = CorrectElement.new.call names, leaf - ideas_to_paths ideas, leaf, names, path - end + ideas = correct_element(names, leaf) + + ideas_to_paths(ideas, leaf, names, path) + end.compact end def ideas_to_paths(ideas, leaf, names, path) - return nil if ideas.empty? - return [path + separator + leaf] if names.include? leaf - ideas.map { |str| path + separator + str } - end - - def find_leaves(path) - dictionary.map do |str| - next unless str.include? "#{path}#{separator}" - str.gsub("#{path}#{separator}", '') - end.compact - end - - def possible_paths(states) - states.map do |state| - state.join separator + if ideas.empty? + nil + elsif names.include?(leaf) + ["#{path}#{separator}#{leaf}"] + else + ideas.map {|str| "#{path}#{separator}#{str}" } end end - def plausible_dimensions(input) - elements = input.split(separator)[0..-2] - elements.each_with_index.map do |element, i| - next if dimensions[i].nil? - CorrectElement.new.call dimensions[i], element - end.compact - end - end - - # parses the elements in each dimension - class ParseDimensions - def initialize(dictionary, separator) - @dictionary = dictionary - @separator = separator - end - - def call - leafless = remove_leaves - dimensions = find_elements leafless - dimensions.map do |elements| - elements.to_set.to_a - end - end - - private - - def remove_leaves - dictionary.map do |a| - elements = a.split(separator) - elements[0..-2] - end.to_set.to_a - end - - def find_elements(leafless) - max_elements = leafless.map(&:size).max - dimensions = Array.new(max_elements) { [] } - (0...max_elements).each do |i| - leafless.each do |elements| - dimensions[i] << elements[i] unless elements[i].nil? - end - end - dimensions - end - - attr_reader :dictionary, :separator - end - - # identifies the elements close to element - class CorrectElement - def initialize - end - - def call(names, element) + def correct_element(names, element) return names if names.size == 1 - str = normalize element - return [str] if names.include? str - checker = ::DidYouMean::SpellChecker.new(dictionary: names) - checker.correct(str) + + str = normalize(element) + + return [str] if names.include?(str) + + ::DidYouMean::SpellChecker.new(dictionary: names).correct(str) end - private - - def normalize(leaf) - str = leaf.dup + def normalize(str) str.downcase! - return str unless str.include? '@' - str.tr!('@', ' ') + str.tr!('@', ' ') if str.include?('@') + str end end end diff --git a/test/did_you_mean/spell_checking/test_method_name_check.rb b/test/did_you_mean/spell_checking/test_method_name_check.rb index f3a6b1c7c7..6e14e6acc4 100644 --- a/test/did_you_mean/spell_checking/test_method_name_check.rb +++ b/test/did_you_mean/spell_checking/test_method_name_check.rb @@ -137,4 +137,11 @@ class MethodNameCheckTest < Test::Unit::TestCase assert_correction :yield, error.corrections assert_match "Did you mean? yield", error.to_s end + + def test_does_not_suggest_yield + error = assert_raise(NoMethodError) { 1.yeild } + + assert_correction [], error.corrections + assert_not_match(/Did you mean\? +yield/, error.to_s) + end if RUBY_ENGINE != "jruby" end diff --git a/test/did_you_mean/spell_checking/test_require_path_check.rb b/test/did_you_mean/spell_checking/test_require_path_check.rb new file mode 100644 index 0000000000..f67fab0568 --- /dev/null +++ b/test/did_you_mean/spell_checking/test_require_path_check.rb @@ -0,0 +1,32 @@ +require_relative '../helper' + +return if !(RUBY_VERSION >= '2.8.0') + +class RequirePathCheckTest < Test::Unit::TestCase + include DidYouMean::TestHelper + + def test_load_error_from_require_has_suggestions + error = assert_raise LoadError do + require 'open_struct' + end + + assert_correction 'ostruct', error.corrections + assert_match "Did you mean? ostruct", error.to_s + end + + def test_load_error_from_require_for_nested_files_has_suggestions + error = assert_raise LoadError do + require 'net/htt' + end + + assert_correction 'net/http', error.corrections + assert_match "Did you mean? net/http", error.to_s + + error = assert_raise LoadError do + require 'net-http' + end + + assert_correction ['net/http', 'net/https'], error.corrections + assert_match "Did you mean? net/http", error.to_s + end +end diff --git a/test/did_you_mean/test_tree_spell_checker.rb b/test/did_you_mean/test_tree_spell_checker.rb index b61a491e20..9ab1c1bc9e 100644 --- a/test/did_you_mean/test_tree_spell_checker.rb +++ b/test/did_you_mean/test_tree_spell_checker.rb @@ -1,11 +1,12 @@ -require 'set' -require 'yaml' +# frozen_string_literal: true -require_relative './helper' +require "yaml" + +require_relative "./helper" class TreeSpellCheckerTest < Test::Unit::TestCase - MINI_DIRECTORIES = YAML.load_file(File.expand_path('fixtures/mini_dir.yml', __dir__)) - RSPEC_DIRECTORIES = YAML.load_file(File.expand_path('fixtures/rspec_dir.yml', __dir__)) + MINI_DIRECTORIES = YAML.load_file(File.expand_path("fixtures/mini_dir.yml", __dir__)) + RSPEC_DIRECTORIES = YAML.load_file(File.expand_path("fixtures/rspec_dir.yml", __dir__)) def setup @dictionary = @@ -20,154 +21,150 @@ class TreeSpellCheckerTest < Test::Unit::TestCase spec/models/gfsga_spec.rb spec/controllers/vixen_controller_spec.rb ) - @test_str = 'spek/modeks/confirns/viken_spec.rb' - @tsp = DidYouMean::TreeSpellChecker.new(dictionary: @dictionary) + @test_str = "spek/modeks/confirns/viken_spec.rb" + @tree_spell_checker = DidYouMean::TreeSpellChecker.new(dictionary: @dictionary) end def test_corrupt_root - word = 'test/verbose_formatter_test.rb' - word_error = 'btets/cverbose_formatter_etst.rb suggestions' - tsp = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES) - s = tsp.correct(word_error).first - assert_match s, word + assert_tree_spell "test/verbose_formatter_test.rb", + input: "btets/cverbose_formatter_etst.rb suggestions", + dictionary: MINI_DIRECTORIES end def test_leafless_state - tsp = DidYouMean::TreeSpellChecker.new(dictionary: @dictionary.push('spec/features')) - word = 'spec/modals/confirms/efgh_spec.rb' - word_error = 'spec/modals/confirXX/efgh_spec.rb' - s = tsp.correct(word_error).first - assert_equal s, word - s = tsp.correct('spec/featuresXX') - assert_equal 'spec/features', s.first + assert_tree_spell "spec/modals/confirms/efgh_spec.rb", + input: "spec/modals/confirXX/efgh_spec.rb", + dictionary: [*@dictionary, "spec/features"] + + assert_tree_spell "spec/features", + input: "spec/featuresXX", + dictionary: [*@dictionary, "spec/features"] end def test_rake_dictionary - dict = %w(parallel:prepare parallel:create parallel:rake parallel:migrate) - word_error = 'parallel:preprare' - tsp = DidYouMean::TreeSpellChecker.new(dictionary: dict, separator: ':') - s = tsp.correct(word_error).first - assert_match s, 'parallel:prepare' + assert_tree_spell "parallel:prepare", + input: "parallel:preprare", + dictionary: %w[parallel:prepare parallel:create parallel:rake parallel:migrate], + separator: ":" end def test_special_words_mini - tsp = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES) - special_words_mini.each do |word, word_error| - s = tsp.correct(word_error).first - assert_match s, word + [ + %w(test/fixtures/book.rb test/fixture/book.rb), + %w(test/edit_distance/jaro_winkler_test.rb test/edit_distace/jaro_winkler_test.rb), + %w(test/edit_distance/jaro_winkler_test.rb teste/dit_distane/jaro_winkler_test.rb), + %w(test/fixtures/book.rb test/fixturWes/book.rb), + %w(test/test_helper.rb tes!t/test_helper.rb), + %w(test/fixtures/book.rb test/hfixtures/book.rb), + %w(test/edit_distance/jaro_winkler_test.rb test/eidt_distance/jaro_winkler_test.@rb), + %w(test/spell_checker_test.rb test/spell_checke@r_test.rb), + %w(test/tree_spell_human_typo_test.rb testt/ree_spell_human_typo_test.rb), + %w(test/edit_distance/jaro_winkler_test.rb test/edit_distance/jaro_winkler_tuest.rb), + ].each do |expected, user_input| + assert_tree_spell expected, input: user_input, dictionary: MINI_DIRECTORIES + end + + [ + %w(test/spell_checking/variable_name_check_test.rb test/spell_checking/vriabl_ename_check_test.rb), + %w(test/spell_checking/key_name_check_test.rb tesit/spell_checking/key_name_choeck_test.rb), + ].each do |expected, user_input| + assert_equal expected, DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES).correct(user_input)[0] end end def test_special_words_rspec - tsp = DidYouMean::TreeSpellChecker.new(dictionary: RSPEC_DIRECTORIES) - special_words_rspec.each do |word, word_error| - s = tsp.correct(word_error) - assert_match s.first, word + [ + %w(spec/rspec/core/formatters/exception_presenter_spec.rb spec/rspec/core/formatters/eception_presenter_spec.rb), + %w(spec/rspec/core/metadata_spec.rb spec/rspec/core/metadata_spe.crb), + %w(spec/rspec/core/ordering_spec.rb spec/spec/core/odrering_spec.rb), + %w(spec/support/mathn_integration_support.rb spec/support/mathn_itegrtion_support.rb), + ].each do |expected, user_input| + assert_tree_spell expected, input: user_input, dictionary: RSPEC_DIRECTORIES end end - def special_words_rspec - [ - ['spec/rspec/core/formatters/exception_presenter_spec.rb','spec/rspec/core/formatters/eception_presenter_spec.rb'], - ['spec/rspec/core/ordering_spec.rb', 'spec/spec/core/odrering_spec.rb'], - ['spec/rspec/core/metadata_spec.rb', 'spec/rspec/core/metadata_spe.crb'], - ['spec/support/mathn_integration_support.rb', 'spec/support/mathn_itegrtion_support.rb'] - ] - end - - def special_words_mini - [ - ['test/fixtures/book.rb', 'test/fixture/book.rb'], - ['test/fixtures/book.rb', 'test/fixture/book.rb'], - ['test/edit_distance/jaro_winkler_test.rb', 'test/edit_distace/jaro_winkler_test.rb'], - ['test/edit_distance/jaro_winkler_test.rb', 'teste/dit_distane/jaro_winkler_test.rb'], - ['test/fixtures/book.rb', 'test/fixturWes/book.rb'], - ['test/test_helper.rb', 'tes!t/test_helper.rb'], - ['test/fixtures/book.rb', 'test/hfixtures/book.rb'], - ['test/edit_distance/jaro_winkler_test.rb', 'test/eidt_distance/jaro_winkler_test.@rb'], - ['test/spell_checker_test.rb', 'test/spell_checke@r_test.rb'], - ['test/tree_spell_human_typo_test.rb', 'testt/ree_spell_human_typo_test.rb'], - ['test/spell_checking/variable_name_check_test.rb', 'test/spell_checking/vriabl_ename_check_test.rb'], - ['test/spell_checking/key_name_check_test.rb', 'tesit/spell_checking/key_name_choeck_test.rb'], - ['test/edit_distance/jaro_winkler_test.rb', 'test/edit_distance/jaro_winkler_tuest.rb'] - ] - end - def test_file_in_root - word = 'test/spell_checker_test.rb' - word_error = 'test/spell_checker_test.r' - suggestions = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES).correct word_error - assert_equal word, suggestions.first + assert_tree_spell "test/spell_checker_test.rb", input: "test/spell_checker_test.r", dictionary: MINI_DIRECTORIES end def test_no_plausible_states - word_error = 'testspell_checker_test.rb' - suggestions = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES).correct word_error - assert_equal [], suggestions + assert_tree_spell [], input: "testspell_checker_test.rb", dictionary: MINI_DIRECTORIES end def test_no_plausible_states_with_augmentation - word_error = 'testspell_checker_test.rb' - suggestions = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES).correct word_error - assert_equal [], suggestions - suggestions = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES, augment: true).correct word_error - assert_equal 'test/spell_checker_test.rb', suggestions.first + assert_tree_spell [], input: "testspell_checker_test.rb", dictionary: MINI_DIRECTORIES + + suggestions = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES, augment: true).correct("testspell_checker_test.rb") + + assert_equal suggestions.first, "test/spell_checker_test.rb" end def test_no_idea_with_augmentation - word_error = 'test/spell_checking/key_name.rb' - suggestions = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES).correct word_error - assert_equal [], suggestions - suggestions = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES, augment: true).correct word_error - assert_equal 'test/spell_checking/key_name_check_test.rb', suggestions.first + assert_tree_spell [], input: "test/spell_checking/key_name.rb", dictionary: MINI_DIRECTORIES + + suggestions = DidYouMean::TreeSpellChecker.new(dictionary: MINI_DIRECTORIES, augment: true).correct("test/spell_checking/key_name.rb") + + assert_equal suggestions.first, "test/spell_checking/key_name_check_test.rb" end def test_works_out_suggestions - exp = ['spec/models/concerns/vixen_spec.rb', - 'spec/models/concerns/vixenus_spec.rb'] - suggestions = @tsp.correct(@test_str) - assert_equal suggestions.to_set, exp.to_set + assert_tree_spell %w(spec/models/concerns/vixen_spec.rb spec/models/concerns/vixenus_spec.rb), + input: "spek/modeks/confirns/viken_spec.rb", + dictionary: %w(spec/models/concerns/vixen_spec.rb spec/models/concerns/vixenus_spec.rb) end def test_works_when_input_is_correct - correct_input = 'spec/models/concerns/vixenus_spec.rb' - suggestions = @tsp.correct correct_input - assert_equal suggestions.first, correct_input + assert_tree_spell "spec/models/concerns/vixenus_spec.rb", + input: "spec/models/concerns/vixenus_spec.rb", + dictionary: @dictionary end def test_find_out_leaves_in_a_path - path = 'spec/modals/confirms' - names = @tsp.send(:find_leaves, path) - assert_equal names.to_set, %w(abcd_spec.rb efgh_spec.rb).to_set + names = @tree_spell_checker.find_leaves("spec/modals/confirms") + + assert_equal %w[abcd_spec.rb efgh_spec.rb], names end def test_works_out_nodes - exp_paths = ['spec/models/concerns', - 'spec/models/confirms', - 'spec/modals/concerns', - 'spec/modals/confirms', - 'spec/controllers/concerns', - 'spec/controllers/confirms'].to_set - states = @tsp.send(:parse_dimensions) - nodes = states[0].product(*states[1..-1]) - paths = @tsp.send(:possible_paths, nodes) - assert_equal paths.to_set, exp_paths.to_set + exp_paths = ["spec/models/concerns", + "spec/models/confirms", + "spec/modals/concerns", + "spec/modals/confirms", + "spec/controllers/concerns", + "spec/controllers/confirms"] + + states = @tree_spell_checker.dimensions + nodes = states[0].product(*states[1..-1]) + paths = @tree_spell_checker.possible_paths(nodes) + + assert_equal paths, exp_paths end def test_works_out_state_space - suggestions = @tsp.send(:plausible_dimensions, @test_str) - assert_equal suggestions, [["spec"], ["models", "modals"], ["confirms", "concerns"]] + suggestions = @tree_spell_checker.plausible_dimensions(@test_str) + + assert_equal [["spec"], %w[models modals], %w[confirms concerns]], suggestions end def test_parses_dictionary - states = @tsp.send(:parse_dimensions) - assert_equal states, [["spec"], ["models", "modals", "controllers"], ["concerns", "confirms"]] + states = @tree_spell_checker.dimensions + + assert_equal [["spec"], %w[models modals controllers], %w[concerns confirms]], states end def test_parses_elementary_dictionary - dictionary = ['spec/models/user_spec.rb', 'spec/services/account_spec.rb'] - tsp = DidYouMean::TreeSpellChecker.new(dictionary: dictionary) - states = tsp.send(:parse_dimensions) - assert_equal states, [['spec'], ['models', 'services']] + dimensions = DidYouMean::TreeSpellChecker + .new(dictionary: %w(spec/models/user_spec.rb spec/services/account_spec.rb)) + .dimensions + + assert_equal [["spec"], %w[models services]], dimensions + end + + private + + def assert_tree_spell(expected, input:, dictionary:, separator: "/") + suggestions = DidYouMean::TreeSpellChecker.new(dictionary: dictionary, separator: separator).correct(input) + + assert_equal Array(expected), suggestions, "Expected to suggest #{expected}, but got #{suggestions.inspect}" end end diff --git a/test/did_you_mean/test_verbose_formatter.rb b/test/did_you_mean/test_verbose_formatter.rb index 92ea9a1c16..d8f2f46273 100644 --- a/test/did_you_mean/test_verbose_formatter.rb +++ b/test/did_you_mean/test_verbose_formatter.rb @@ -3,6 +3,7 @@ require_relative './helper' class VerboseFormatterTest < Test::Unit::TestCase def setup require_relative File.join(DidYouMean::TestHelper.root, 'verbose') + DidYouMean.formatter = DidYouMean::VerboseFormatter.new end diff --git a/test/did_you_mean/tree_spell/human_typo.rb b/test/did_you_mean/tree_spell/human_typo.rb index 302d4d6902..9d410a17c4 100644 --- a/test/did_you_mean/tree_spell/human_typo.rb +++ b/test/did_you_mean/tree_spell/human_typo.rb @@ -4,6 +4,9 @@ module TreeSpell # Simulate an error prone human typist # see doc/human_typo_api.md for the api description class HumanTypo + POPULAR_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ?<>,.!`+=-_":;@#$%^&*()'.split("").freeze + ACTION_TYPES = %i(insert transpose delete substitute).freeze + def initialize(input, lambda: 0.05) @input = input check_input @@ -15,7 +18,7 @@ module TreeSpell @word = input.dup i_place = initialize_i_place loop do - action = action_type + action = ACTION_TYPES.sample @word = make_change action, i_place @len = word.length i_place += exponential @@ -41,40 +44,17 @@ module TreeSpell (rand / (lambda / 2)).to_i end - def rand_char - popular_chars = alphabetic_characters + special_characters - n = popular_chars.length - popular_chars[rand(n)] - end - - def alphabetic_characters - ('a'..'z').to_a.join + ('A'..'Z').to_a.join - end - - def special_characters - '?<>,.!`+=-_":;@#$%^&*()' - end - - def toss - return +1 if rand >= 0.5 - -1 - end - - def action_type - [:insert, :transpose, :delete, :substitute][rand(4)] - end - def make_change(action, i_place) cw = ChangeWord.new(word) case action when :delete cw.deletion(i_place) when :insert - cw.insertion(i_place, rand_char) + cw.insertion(i_place, POPULAR_CHARS.sample) when :substitute - cw.substitution(i_place, rand_char) + cw.substitution(i_place, POPULAR_CHARS.sample) when :transpose - cw.transposition(i_place, toss) + cw.transposition(i_place, rand >= 0.5 ? +1 : -1) end end