did_you_mean 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +48 -0
  3. data/CHANGELOG.md +75 -75
  4. data/Gemfile +2 -1
  5. data/README.md +2 -32
  6. data/Rakefile +4 -5
  7. data/appveyor.yml +25 -0
  8. data/did_you_mean.gemspec +6 -4
  9. data/documentation/CHANGELOG.md.erb +8 -0
  10. data/documentation/changelog_generator.rb +34 -0
  11. data/documentation/human_typo_api.md +20 -0
  12. data/documentation/tree_spell_algorithm.md +82 -0
  13. data/documentation/tree_spell_checker_api.md +24 -0
  14. data/lib/did_you_mean.rb +17 -16
  15. data/lib/did_you_mean/experimental.rb +2 -2
  16. data/lib/did_you_mean/experimental/initializer_name_correction.rb +1 -1
  17. data/lib/did_you_mean/experimental/ivar_name_correction.rb +3 -1
  18. data/lib/did_you_mean/levenshtein.rb +1 -1
  19. data/lib/did_you_mean/spell_checker.rb +7 -7
  20. data/lib/did_you_mean/spell_checkers/key_error_checker.rb +8 -2
  21. data/lib/did_you_mean/spell_checkers/method_name_checker.rb +14 -6
  22. data/lib/did_you_mean/spell_checkers/name_error_checkers.rb +2 -2
  23. data/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb +5 -5
  24. data/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb +1 -1
  25. data/lib/did_you_mean/tree_spell_checker.rb +137 -0
  26. data/lib/did_you_mean/verbose.rb +2 -2
  27. data/lib/did_you_mean/version.rb +1 -1
  28. data/test/core_ext/test_name_error_extension.rb +48 -0
  29. data/test/edit_distance/{jaro_winkler_test.rb → test_jaro_winkler.rb} +2 -2
  30. data/test/fixtures/mini_dir.yml +15 -0
  31. data/test/fixtures/rspec_dir.yml +112 -0
  32. data/test/helper.rb +29 -0
  33. data/test/spell_checking/{class_name_check_test.rb → test_class_name_check.rb} +12 -10
  34. data/test/spell_checking/{key_name_check_test.rb → test_key_name_check.rb} +18 -8
  35. data/test/spell_checking/{method_name_check_test.rb → test_method_name_check.rb} +17 -15
  36. data/test/spell_checking/{uncorrectable_name_check_test.rb → test_uncorrectable_name_check.rb} +3 -3
  37. data/test/spell_checking/{variable_name_check_test.rb → test_variable_name_check.rb} +18 -16
  38. data/test/{spell_checker_test.rb → test_spell_checker.rb} +2 -2
  39. data/test/test_tree_spell_checker.rb +173 -0
  40. data/test/test_verbose_formatter.rb +21 -0
  41. data/test/tree_spell/change_word.rb +61 -0
  42. data/test/tree_spell/human_typo.rb +89 -0
  43. data/test/tree_spell/test_change_word.rb +38 -0
  44. data/test/tree_spell/test_explore.rb +128 -0
  45. data/test/tree_spell/test_human_typo.rb +24 -0
  46. metadata +47 -58
  47. data/.travis.yml +0 -23
  48. data/test/core_ext/name_error_extension_test.rb +0 -51
  49. data/test/experimental/initializer_name_correction_test.rb +0 -15
  50. data/test/experimental/method_name_checker_test.rb +0 -13
  51. data/test/test_helper.rb +0 -13
  52. data/test/verbose_formatter_test.rb +0 -22
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'benchmark-ips'
7
- gem 'benchmark-driver'
7
+ gem 'benchmark_driver'
8
8
  gem 'memory_profiler'
9
9
  gem 'jaro_winkler', '>= 1.4.0'
10
+ gem 'test-unit'
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # did_you_mean [![Gem Version](https://badge.fury.io/rb/did_you_mean.svg)](https://rubygems.org/gems/did_you_mean) [![Build Status](https://travis-ci.org/yuki24/did_you_mean.svg?branch=master)](https://travis-ci.org/yuki24/did_you_mean)
1
+ # did_you_mean [![Gem Version](https://badge.fury.io/rb/did_you_mean.svg)](https://rubygems.org/gems/did_you_mean) [![Build Status](https://travis-ci.org/ruby/did_you_mean.svg?branch=master)](https://travis-ci.org/ruby/did_you_mean)
2
2
 
3
3
  ## Installation
4
4
 
@@ -61,36 +61,6 @@ hash.fetch(:fooo)
61
61
  # Did you mean? :foo
62
62
  ```
63
63
 
64
- ## Experimental Features
65
-
66
- Aside from the basic features above, the `did_you_mean` gem comes with experimental features. They can be enabled by calling `require 'did_you_mean/experimental'`.
67
-
68
- Note that **these experimental features should never be enabled in production as they would impact Ruby's performance and use some unstable Ruby APIs.**
69
-
70
- ### Correcting an Instance Variable When It's Incorrectly Spelled
71
-
72
- ```ruby
73
- require 'did_you_mean/experimental'
74
-
75
- @full_name = "Yuki Nishijima"
76
- @full_anme.split(" ")
77
- # => NoMethodError: undefined method `split' for nil:NilClass
78
- # Did you mean? @full_name
79
- ```
80
-
81
- ### Displaying a Warning When `initialize` is Incorrectly Spelled
82
-
83
- ```ruby
84
- require 'did_you_mean/experimental'
85
-
86
- class Person
87
- def intialize
88
- ...
89
- end
90
- end
91
- # => warning: intialize might be misspelled, perhaps you meant initialize?
92
- ```
93
-
94
64
  ## Verbose Formatter
95
65
 
96
66
  This verbose formatter changes the error message format to take more lines/spaces so it'll be slightly easier to read the suggestions. This formatter can totally be used in any environment including production.
@@ -183,7 +153,7 @@ bundle exec benchmark-driver benchmark/speed.yml --rbenv '2.6.0 --jit;2.6.0;2.5.
183
153
 
184
154
  ## Contributing
185
155
 
186
- 1. Fork it (http://github.com/yuki24/did_you_mean/fork)
156
+ 1. Fork it (https://github.com/ruby/did_you_mean/fork)
187
157
  2. Create your feature branch (`git checkout -b my-new-feature`)
188
158
  3. Commit your changes (`git commit -am 'Add some feature'`)
189
159
  4. Make sure all tests pass (`bundle exec rake`)
data/Rakefile CHANGED
@@ -4,20 +4,19 @@ require 'rake/testtask'
4
4
  Rake::TestTask.new do |task|
5
5
  task.libs << "test"
6
6
 
7
- task.test_files = Dir['test/**/*_test.rb'].reject {|path| /(experimental)/ =~ path }
7
+ task.test_files = Dir['test/**/test_*.rb'].reject {|path| path.end_with?("test_explore.rb") }
8
8
  task.verbose = true
9
9
  task.warning = true
10
10
  end
11
11
 
12
- Rake::TestTask.new("test:experimental") do |task|
12
+ Rake::TestTask.new("test:explore") do |task|
13
13
  task.libs << "test"
14
- task.pattern = 'test/experimental/**/*_test.rb'
14
+ task.pattern = 'test/tree_spell/test_explore.rb'
15
15
  task.verbose = true
16
16
  task.warning = true
17
- task.ruby_opts << "-rdid_you_mean/experimental"
18
17
  end
19
18
 
20
- task default: %i(test test:experimental)
19
+ task default: %i(test)
21
20
 
22
21
  namespace :test do
23
22
  namespace :accuracy do
@@ -0,0 +1,25 @@
1
+ install:
2
+ - set PATH=C:\Ruby26-x64\bin;%PATH%
3
+
4
+ build: off
5
+
6
+ branches:
7
+ only:
8
+ - master
9
+
10
+ environment:
11
+ ruby_version: "24-%Platform%"
12
+ zlib_version: "1.2.11"
13
+ matrix:
14
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
15
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
16
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
17
+
18
+ before_test:
19
+ - ruby -v
20
+ - gem -v
21
+ - bundle -v
22
+ - gem uni did_you_mean
23
+
24
+ test_script:
25
+ - rake
@@ -1,7 +1,11 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'did_you_mean/version'
4
+ begin
5
+ require_relative "lib/did_you_mean/version"
6
+ rescue LoadError # Fallback to load version file in ruby core repository
7
+ require_relative "version"
8
+ end
5
9
 
6
10
  Gem::Specification.new do |spec|
7
11
  spec.name = "did_you_mean"
@@ -10,7 +14,7 @@ Gem::Specification.new do |spec|
10
14
  spec.email = ["mail@yukinishijima.net"]
11
15
  spec.summary = '"Did you mean?" experience in Ruby'
12
16
  spec.description = 'The gem that has been saving people from typos since 2014.'
13
- spec.homepage = "https://github.com/yuki24/did_you_mean"
17
+ spec.homepage = "https://github.com/ruby/did_you_mean"
14
18
  spec.license = "MIT"
15
19
 
16
20
  spec.files = `git ls-files`.split($/).reject{|path| path.start_with?('evaluation/') }
@@ -19,7 +23,5 @@ Gem::Specification.new do |spec|
19
23
 
20
24
  spec.required_ruby_version = '>= 2.5.0'
21
25
 
22
- spec.add_development_dependency "bundler"
23
26
  spec.add_development_dependency "rake"
24
- spec.add_development_dependency "minitest"
25
27
  end
@@ -0,0 +1,8 @@
1
+ <% releases.each do |release| %>
2
+ ## [<%= release.name %>](https://github.com/<%= repository %>/tree/<%= release.tag_name %>)
3
+
4
+ _<sup>released at <%= release.published_at %></sup>_
5
+
6
+ <%= release.body.gsub(/\r\n/, "\n") %>
7
+
8
+ <% end %>
@@ -0,0 +1,34 @@
1
+ require 'octokit'
2
+ require 'reverse_markdown'
3
+ require 'erb'
4
+
5
+ class ChangeLogGenerator
6
+ attr :repository, :template_path, :changelog_path
7
+
8
+ def initialize(repository, template_path: "CHANGELOG.md.erb", changelog_path: "CHANGELOG.md")
9
+ @repository = repository
10
+ @template_path = template_path
11
+ @changelog_path = changelog_path
12
+ end
13
+
14
+ def generate_and_save!
15
+ changelog_in_md = ERB.new(template).result(binding)
16
+ changelog_in_html = Octokit.markdown(changelog_in_md, context: repository, mode: "gfm")
17
+
18
+ File.open(changelog_path, 'w') do |file|
19
+ file.write ReverseMarkdown.convert(changelog_in_html, github_flavored: true)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def template
26
+ open("#{__dir__}/#{template_path}").read
27
+ end
28
+
29
+ def releases
30
+ @releases ||= Octokit.releases(repository)
31
+ end
32
+ end
33
+
34
+ ChangeLogGenerator.new("ruby/did_you_mean").generate_and_save!
@@ -0,0 +1,20 @@
1
+ # HumanTypo API
2
+ ## Description
3
+ Simulate an error prone human typist. Assumes typographical errors are Poisson distributed and
4
+ each error is either a deletion, insertion, substitution, or transposition
5
+ ## Initialization
6
+ ```
7
+ def initialize(input, lambda: 0.05)
8
+ end
9
+ ```
10
+ where
11
+ ### input: A string with the word to be corrupted.
12
+ ## lambda: Error rate of the poisson process
13
+ The default of 0.05 corresponds to one error every 20 characters, and is thought to approximate the average, competent typist
14
+ ## Methods
15
+ ```
16
+ def call
17
+ end
18
+ ```
19
+ Returns a word with typographical errors.
20
+
@@ -0,0 +1,82 @@
1
+ # TreeSpellChecker Algorithm
2
+ ## Overview
3
+ The algorithm is designed to work on a dictionary that has a rooted tree structure.
4
+
5
+ The algorithm treats the problem as a hidden state system, which tries to identify the true state of the input. Due to typographical errors, the state of the input is a hidden version of the true system state. Each word in the dictionary is mapped to a multi-dimensional state, with the first dimension being being the root, the second dimension being the next branch, and so on. Each dimension is discrete with a finite number of elements. The first dimension corresponds to the root, so only has one element.
6
+
7
+ The algorithm assumes the input state has the correct structure, and so generates the state of the input. It starts with the root of the input word and maps it to the root element. It then looks at the value of second dimension of the input word and chooses closest elements of the possible second dimension elements. It then continues to the third and higher dimensions. It terminates when it has worked out possible elements corresponding to the highest dimension of the input word. At this point it has the possible elements at for each dimension of the input word. It then generates all possible legitimate states from these elements. Finally it then compares the possible leaves at the end of these legitimate states with the leaf of the input word. From this process it produces suggested states.
8
+
9
+ ## Accuracy
10
+ The accuracy of the algorithm was tested using the HumanTypo class. It simulates a human typist by assuming that errors are Poisson distributed at a rate of one typo per 20 characters. Typos can be either a deletion, an insertion, substitution or a transposition.
11
+
12
+ I ran 10,000 repititions on both the `test` directory of the ```did_you_mean``` gem and on the ```spec``` directory of the ```rspec-core``` gem.
13
+
14
+ The results were as follows:
15
+ ```
16
+ Minitest Summary
17
+ --------------------------------------------------------------------------------
18
+ Method | First Time (%) Mean Suggestions Failures (%)
19
+ --------------------------------------------------------------------------------
20
+ Tree 98.0 1.1 2.0
21
+ Standard 98.1 2.2 1.6
22
+ Augmented 100.0 1.1 0.0
23
+ ```
24
+ and
25
+ ```
26
+ Rspec Summary
27
+ --------------------------------------------------------------------------------
28
+ Method | First Time (%) Mean Suggestions Failures (%)
29
+ --------------------------------------------------------------------------------
30
+ Tree 94.7 1.0 5.3
31
+ Standard 98.2 4.2 1.1
32
+ Augmented 99.7 1.2 0.2
33
+ ```
34
+ As well, I checked the results on the ```test``` directory with ```HumanTypo``` generating errors at three times the rate:
35
+ ```
36
+ Minitest Summary (lambda = 0.15)
37
+ --------------------------------------------------------------------------------
38
+ Method | First Time (%) Mean Suggestions Failures (%)
39
+ --------------------------------------------------------------------------------
40
+ Tree 88.9 1.0 11.0
41
+ Standard 95.0 1.4 4.3
42
+ Combined 99.0 1.0 0.8
43
+ ```
44
+ In all cases, the tree speller, when augmented by the standard spell checker performed with higher accuracy, and giving far fewer suggestions.
45
+
46
+ ## Execution Speed
47
+
48
+ I tested the execution time on the ```test ``` directory:
49
+ ```
50
+ Testing execution time of Standard
51
+ Average time (ms): 5.2
52
+
53
+ Testing execution time of Tree
54
+ Average time (ms): 1.1
55
+
56
+ Testing execution time of Augmented Tree
57
+ Average time (ms): 1.2
58
+ ```
59
+ and on the ```spec``` directory
60
+ ```
61
+
62
+ Testing execution time of Standard
63
+ Average time (ms): 40.6
64
+
65
+ Testing execution time of Tree
66
+ Average time (ms): 2.7
67
+
68
+ Testing execution time of Augmented Tree
69
+ Average time (ms): 4.5
70
+ ```
71
+
72
+ I was surprised by how much faster the tree checker was compared to the standard checker. I think the reason is that the predominant computational load will scale with O(log n) where n is the total number of words in the dictionary. My reasoning is that the algorithm very quickly prunes out states as it moves through the dimensions.
73
+ ## Augmentation option
74
+ Given the major difference in speed between the standard and tree checker, and the likelihood that the disparity will grow rapidly with the size of the dictionary, then I suspect for some applications, it will not be not practicable to augment the tree checker by using the standard checker when the tree checker fails to find a suggestion. Accordingly, I have added an option, ```:augment?```. The default is nil, but if true, then the standard checker is used if there are no suggestions.
75
+ ## Generation of Performance data
76
+ This is done using ```test/tree_spell/explore_test.rb```. This is not a proper test file in that there are no assertions in it. As well, it takes over ten minutes to run, accordingly, I have disabled it by setting the constant TREE_SPELL_EXPLORE to false at the top of the file. To run the file, set TREE_SPELL_EXPLORE to true. It is also possible to run quick assessments by using a smaller value of n_repeat in the various tests.
77
+ ## Future Work
78
+ I have identified two categories of remaining errors. The first class is when one of the elements is corrupted to being very small. Then the standard checker does not suggest the correct element, e.g. if an element is ```core``` and it is reduced to ```co```, the standard speller will not make a suggestion. The second class of error is when the structure of the word is broken because one of the separators has been removed. I think it might be possible to remove the first type of error and dramatically reduce the second type of error in a future version. This would be done as follows:
79
+ - At each level, choose the dictionary element with the smallest distance.
80
+ - This will work well unless the structure is broken, in which case it could return a wildly wrong suggestion.
81
+ - To guard against this, the suggestion could be checked against the input word using the standard checker. If the standard checker rejects the suggestion, then it is assumed the structure is broken.
82
+ - A large proportion of the time, a broken structure will be just due to one separator missing, and the order of the elements will not be affected. Accordingly, the structure can be fixed by comparing the input elements with a concatenation of two levels of the dictionary elements. It would be possible to use the same idea to fix more than one separator missing, but this could quickly become computationally expensive.
@@ -0,0 +1,24 @@
1
+ # TreeSpellChecker API
2
+ ## Description
3
+
4
+ ## Initialization
5
+ ```
6
+ def initialize(dictionary:, separator: '/', augment: nil)
7
+ end
8
+ ```
9
+ where
10
+ ####dictionary: The dictionary is a list of possible words
11
+ * that are used to correct a misspelling
12
+ * The dictionary must be tree structured with a single character separator
13
+ * e.g 'spec/models/goals_spec_rb'.
14
+ ####separator: A single charactor. Cannot be cannot be alphabetical, '@' or '.'.
15
+ ####augment: When set to true, the checker will used the standard ```SpellChecker``` to find possible suggestions.
16
+ ## Methods
17
+ ```
18
+ def correct(input)
19
+ end
20
+ ```
21
+ where
22
+ ####input: Is the input word to be corrected.
23
+
24
+
@@ -1,13 +1,13 @@
1
- require "did_you_mean/version"
2
- require "did_you_mean/core_ext/name_error"
1
+ require_relative "did_you_mean/version"
2
+ require_relative "did_you_mean/core_ext/name_error"
3
3
 
4
- require "did_you_mean/spell_checker"
5
- require 'did_you_mean/spell_checkers/name_error_checkers'
6
- require 'did_you_mean/spell_checkers/method_name_checker'
7
- require 'did_you_mean/spell_checkers/key_error_checker'
8
- require 'did_you_mean/spell_checkers/null_checker'
9
-
10
- require "did_you_mean/formatters/plain_formatter"
4
+ require_relative "did_you_mean/spell_checker"
5
+ require_relative 'did_you_mean/spell_checkers/name_error_checkers'
6
+ require_relative 'did_you_mean/spell_checkers/method_name_checker'
7
+ require_relative 'did_you_mean/spell_checkers/key_error_checker'
8
+ require_relative 'did_you_mean/spell_checkers/null_checker'
9
+ require_relative 'did_you_mean/formatters/plain_formatter'
10
+ require_relative 'did_you_mean/tree_spell_checker'
11
11
 
12
12
  # The +DidYouMean+ gem adds functionality to suggest possible method/class
13
13
  # names upon errors such as +NameError+ and +NoMethodError+. In Ruby 2.3 or
@@ -86,14 +86,15 @@ module DidYouMean
86
86
  # Map of error types and spell checker objects.
87
87
  SPELL_CHECKERS = Hash.new(NullChecker)
88
88
 
89
- SPELL_CHECKERS.merge!({
90
- "NameError" => NameErrorCheckers,
91
- "NoMethodError" => MethodNameChecker,
92
- "KeyError" => KeyErrorChecker
93
- })
89
+ # Adds +DidYouMean+ functionality to an error using a given spell checker
90
+ def self.correct_error(error_class, spell_checker)
91
+ SPELL_CHECKERS[error_class.name] = spell_checker
92
+ error_class.prepend(Correctable) unless error_class < Correctable
93
+ end
94
94
 
95
- NameError.prepend DidYouMean::Correctable
96
- KeyError.prepend DidYouMean::Correctable
95
+ correct_error NameError, NameErrorCheckers
96
+ correct_error KeyError, KeyErrorChecker
97
+ correct_error NoMethodError, MethodNameChecker
97
98
 
98
99
  # Returns the currenctly set formatter. By default, it is set to +DidYouMean::Formatter+.
99
100
  def self.formatter
@@ -1,2 +1,2 @@
1
- require 'did_you_mean/experimental/initializer_name_correction'
2
- require 'did_you_mean/experimental/ivar_name_correction'
1
+ warn "Experimental features in the did_you_mean gem has been removed " \
2
+ "and `require \"did_you_mean/experimental\"' has no effect."
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'did_you_mean/levenshtein'
3
+ require_relative '../levenshtein'
4
4
 
5
5
  module DidYouMean
6
6
  module Experimental
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'did_you_mean'
3
+ require_relative '../../did_you_mean'
4
4
 
5
5
  module DidYouMean
6
6
  module Experimental #:nodoc:
@@ -36,6 +36,8 @@ module DidYouMean
36
36
 
37
37
  @location = no_method_error.backtrace_locations.first
38
38
  @ivar_names = no_method_error.frame_binding.receiver.instance_variables
39
+
40
+ no_method_error.remove_instance_variable(:@frame_binding)
39
41
  end
40
42
 
41
43
  def corrections
@@ -41,7 +41,7 @@ module DidYouMean
41
41
 
42
42
  # detects the minimum value out of three arguments. This method is
43
43
  # faster than `[a, b, c].min` and puts less GC pressure.
44
- # See https://github.com/yuki24/did_you_mean/pull/1 for a performance
44
+ # See https://github.com/ruby/did_you_mean/pull/1 for a performance
45
45
  # benchmark.
46
46
  def min3(a, b, c)
47
47
  if a < b && a < c
@@ -1,11 +1,11 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require "did_you_mean/levenshtein"
4
- require "did_you_mean/jaro_winkler"
3
+ require_relative "levenshtein"
4
+ require_relative "jaro_winkler"
5
5
 
6
6
  module DidYouMean
7
7
  class SpellChecker
8
- def initialize(dictionary: )
8
+ def initialize(dictionary:)
9
9
  @dictionary = dictionary
10
10
  end
11
11
 
@@ -13,14 +13,14 @@ module DidYouMean
13
13
  input = normalize(input)
14
14
  threshold = input.length > 3 ? 0.834 : 0.77
15
15
 
16
- words = @dictionary.select {|word| JaroWinkler.distance(normalize(word), input) >= threshold }
17
- words.reject! {|word| input == word.to_s }
18
- words.sort_by! {|word| JaroWinkler.distance(word.to_s, input) }
16
+ words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), input) >= threshold }
17
+ words.reject! { |word| input == word.to_s }
18
+ words.sort_by! { |word| JaroWinkler.distance(word.to_s, input) }
19
19
  words.reverse!
20
20
 
21
21
  # Correct mistypes
22
22
  threshold = (input.length * 0.25).ceil
23
- corrections = words.select {|c| Levenshtein.distance(normalize(c), input) <= threshold }
23
+ corrections = words.select { |c| Levenshtein.distance(normalize(c), input) <= threshold }
24
24
 
25
25
  # Correct misspells
26
26
  if corrections.empty?
@@ -1,4 +1,4 @@
1
- require "did_you_mean/spell_checker"
1
+ require_relative "../spell_checker"
2
2
 
3
3
  module DidYouMean
4
4
  class KeyErrorChecker
@@ -8,7 +8,13 @@ module DidYouMean
8
8
  end
9
9
 
10
10
  def corrections
11
- @corrections ||= SpellChecker.new(dictionary: @keys).correct(@key).map(&:inspect)
11
+ @corrections ||= exact_matches.empty? ? SpellChecker.new(dictionary: @keys).correct(@key).map(&:inspect) : exact_matches
12
+ end
13
+
14
+ private
15
+
16
+ def exact_matches
17
+ @exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect)
12
18
  end
13
19
  end
14
20
  end