From 66df18c55e929de4d133cd9e71807a70de392ec0 Mon Sep 17 00:00:00 2001 From: Yuki Nishijima Date: Fri, 22 Oct 2021 21:00:23 -0400 Subject: [PATCH] Sync did_you_mean again --- lib/did_you_mean.rb | 12 +++-- lib/did_you_mean/formatter.rb | 35 +++++++++++++ .../formatters/plain_formatter.rb | 35 ++----------- .../formatters/verbose_formatter.rb | 52 +++---------------- lib/did_you_mean/spell_checker.rb | 18 +++---- .../pattern_key_name_checker.rb | 20 +++++++ lib/did_you_mean/verbose.rb | 6 +-- .../test_pattern_key_name_check.rb | 20 +++++++ test/did_you_mean/test_spell_checker.rb | 1 + test/did_you_mean/test_verbose_formatter.rb | 38 -------------- 10 files changed, 103 insertions(+), 134 deletions(-) create mode 100644 lib/did_you_mean/formatter.rb create mode 100644 lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb create mode 100644 test/did_you_mean/spell_checking/test_pattern_key_name_check.rb delete mode 100644 test/did_you_mean/test_verbose_formatter.rb diff --git a/lib/did_you_mean.rb b/lib/did_you_mean.rb index ab7e6b01a8..6a680fe975 100644 --- a/lib/did_you_mean.rb +++ b/lib/did_you_mean.rb @@ -7,7 +7,8 @@ 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/spell_checkers/pattern_key_name_checker' +require_relative 'did_you_mean/formatter' require_relative 'did_you_mean/tree_spell_checker' # The +DidYouMean+ gem adds functionality to suggest possible method/class @@ -97,16 +98,17 @@ module DidYouMean correct_error KeyError, KeyErrorChecker correct_error NoMethodError, MethodNameChecker correct_error LoadError, RequirePathChecker if RUBY_VERSION >= '2.8.0' + correct_error NoMatchingPatternKeyError, PatternKeyNameChecker if defined?(::NoMatchingPatternKeyError) # Returns the currently set formatter. By default, it is set to +DidYouMean::Formatter+. def self.formatter - @@formatter + @formatter end # Updates the primary formatter used to format the suggestions. - def self.formatter=(formatter) - @@formatter = formatter + def self.formatter=(*) + @formatter = formatter end - self.formatter = PlainFormatter.new + @formatter = Formatter.new end diff --git a/lib/did_you_mean/formatter.rb b/lib/did_you_mean/formatter.rb new file mode 100644 index 0000000000..01eb59100a --- /dev/null +++ b/lib/did_you_mean/formatter.rb @@ -0,0 +1,35 @@ +# frozen-string-literal: true + +module DidYouMean + # The +DidYouMean::Formatter+ is the basic, default formatter for the + # gem. The formatter responds to the +message_for+ method and it returns a + # human readable string. + class Formatter + + # Returns a human readable string that contains +corrections+. This + # formatter is designed to be less verbose to not take too much screen + # space while being helpful enough to the user. + # + # @example + # + # formatter = DidYouMean::Formatter.new + # + # # displays suggestions in two lines with the leading empty line + # puts formatter.message_for(["methods", "method"]) + # + # Did you mean? methods + # method + # # => nil + # + # # displays an empty line + # puts formatter.message_for([]) + # + # # => nil + # + def message_for(corrections) + corrections.empty? ? "" : "\nDid you mean? #{corrections.join("\n ")}" + end + end + + PlainFormatter = Formatter +end diff --git a/lib/did_you_mean/formatters/plain_formatter.rb b/lib/did_you_mean/formatters/plain_formatter.rb index e2d995f587..d669588e0f 100644 --- a/lib/did_you_mean/formatters/plain_formatter.rb +++ b/lib/did_you_mean/formatters/plain_formatter.rb @@ -1,33 +1,4 @@ -# frozen-string-literal: true +require_relative '../formatter' -module DidYouMean - # The +DidYouMean::PlainFormatter+ is the basic, default formatter for the - # gem. The formatter responds to the +message_for+ method and it returns a - # human readable string. - class PlainFormatter - - # Returns a human readable string that contains +corrections+. This - # formatter is designed to be less verbose to not take too much screen - # space while being helpful enough to the user. - # - # @example - # - # formatter = DidYouMean::PlainFormatter.new - # - # # displays suggestions in two lines with the leading empty line - # puts formatter.message_for(["methods", "method"]) - # - # Did you mean? methods - # method - # # => nil - # - # # displays an empty line - # puts formatter.message_for([]) - # - # # => nil - # - def message_for(corrections) - corrections.empty? ? "" : "\nDid you mean? #{corrections.join("\n ")}" - end - end -end +warn "`require 'did_you_mean/formatters/plain_formatter'` is deprecated. Please `require 'did_you_mean/formatter'` " \ + "instead." diff --git a/lib/did_you_mean/formatters/verbose_formatter.rb b/lib/did_you_mean/formatters/verbose_formatter.rb index b8fe214d57..8ee98fa070 100644 --- a/lib/did_you_mean/formatters/verbose_formatter.rb +++ b/lib/did_you_mean/formatters/verbose_formatter.rb @@ -1,49 +1,9 @@ +warn "`require 'did_you_mean/formatters/verbose_formatter'` is deprecated and falls back to the default formatter. " + +require_relative '../formatter' + # frozen-string-literal: true - module DidYouMean - # The +DidYouMean::VerboseFormatter+ uses extra empty lines to make the - # suggestion stand out more in the error message. - # - # In order to activate the verbose formatter, - # - # @example - # - # OBject - # # => NameError: uninitialized constant OBject - # # Did you mean? Object - # - # require 'did_you_mean/verbose' - # - # OBject - # # => NameError: uninitialized constant OBject - # # - # # Did you mean? Object - # # - # - class VerboseFormatter - - # Returns a human readable string that contains +corrections+. This - # formatter is designed to be less verbose to not take too much screen - # space while being helpful enough to the user. - # - # @example - # - # formatter = DidYouMean::PlainFormatter.new - # - # puts formatter.message_for(["methods", "method"]) - # - # - # Did you mean? methods - # method - # - # # => nil - # - def message_for(corrections) - return "" if corrections.empty? - - output = "\n\n Did you mean? ".dup - output << corrections.join("\n ") - output << "\n " - end - end + # For compatibility: + VerboseFormatter = Formatter end diff --git a/lib/did_you_mean/spell_checker.rb b/lib/did_you_mean/spell_checker.rb index e5106abba2..37da2fc7a6 100644 --- a/lib/did_you_mean/spell_checker.rb +++ b/lib/did_you_mean/spell_checker.rb @@ -10,25 +10,25 @@ module DidYouMean end def correct(input) - input = normalize(input) - threshold = input.length > 3 ? 0.834 : 0.77 + normalized_input = normalize(input) + threshold = normalized_input.length > 3 ? 0.834 : 0.77 - words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), input) >= threshold } - words.reject! { |word| input == word.to_s } - words.sort_by! { |word| JaroWinkler.distance(word.to_s, input) } + words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), normalized_input) >= threshold } + words.reject! { |word| input.to_s == word.to_s } + words.sort_by! { |word| JaroWinkler.distance(word.to_s, normalized_input) } words.reverse! # Correct mistypes - threshold = (input.length * 0.25).ceil - corrections = words.select { |c| Levenshtein.distance(normalize(c), input) <= threshold } + threshold = (normalized_input.length * 0.25).ceil + corrections = words.select { |c| Levenshtein.distance(normalize(c), normalized_input) <= threshold } # Correct misspells if corrections.empty? corrections = words.select do |word| word = normalize(word) - length = input.length < word.length ? input.length : word.length + length = normalized_input.length < word.length ? normalized_input.length : word.length - Levenshtein.distance(word, input) < length + Levenshtein.distance(word, normalized_input) < length end.first(1) end diff --git a/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb b/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb new file mode 100644 index 0000000000..ed263c8f93 --- /dev/null +++ b/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb @@ -0,0 +1,20 @@ +require_relative "../spell_checker" + +module DidYouMean + class PatternKeyNameChecker + def initialize(no_matching_pattern_key_error) + @key = no_matching_pattern_key_error.key + @keys = no_matching_pattern_key_error.matchee.keys + end + + def corrections + @corrections ||= exact_matches.empty? ? SpellChecker.new(dictionary: @keys).correct(@key).map(&:inspect) : exact_matches + end + + private + + def exact_matches + @exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect) + end + end +end diff --git a/lib/did_you_mean/verbose.rb b/lib/did_you_mean/verbose.rb index 4e86f167ea..1ff19aef80 100644 --- a/lib/did_you_mean/verbose.rb +++ b/lib/did_you_mean/verbose.rb @@ -1,4 +1,2 @@ -require_relative '../did_you_mean' -require_relative 'formatters/verbose_formatter' - -DidYouMean.formatter = DidYouMean::VerboseFormatter.new +warn "The verbose formatter has been removed and now `require 'did_you_mean/verbose'` has no effect. Please " \ + "remove this call." diff --git a/test/did_you_mean/spell_checking/test_pattern_key_name_check.rb b/test/did_you_mean/spell_checking/test_pattern_key_name_check.rb new file mode 100644 index 0000000000..2b0752a56a --- /dev/null +++ b/test/did_you_mean/spell_checking/test_pattern_key_name_check.rb @@ -0,0 +1,20 @@ +require_relative '../helper' + +return if !defined?(::NoMatchingPatternKeyError) + +class PatternKeyNameCheckTest < Test::Unit::TestCase + include DidYouMean::TestHelper + + def test_corrects_hash_key_name_with_single_pattern_match + error = assert_raise(NoMatchingPatternKeyError) do + eval(<<~RUBY, binding, __FILE__, __LINE__) + hash = {foo: 1, bar: 2, baz: 3} + hash => {fooo:} + fooo = 1 # suppress "unused variable: fooo" warning + RUBY + end + + assert_correction ":foo", error.corrections + assert_match "Did you mean? :foo", error.to_s + end +end diff --git a/test/did_you_mean/test_spell_checker.rb b/test/did_you_mean/test_spell_checker.rb index 98460b4d94..8445380de3 100644 --- a/test/did_you_mean/test_spell_checker.rb +++ b/test/did_you_mean/test_spell_checker.rb @@ -10,6 +10,7 @@ class SpellCheckerTest < Test::Unit::TestCase assert_spell 'eval', input: 'veal', dictionary: ['email', 'fail', 'eval'] assert_spell 'sub!', input: 'suv!', dictionary: ['sub', 'gsub', 'sub!'] assert_spell 'sub', input: 'suv', dictionary: ['sub', 'gsub', 'sub!'] + assert_spell 'Foo', input: 'FOo', dictionary: ['Foo', 'FOo'] assert_spell %w(gsub! gsub), input: 'gsuv!', dictionary: %w(sub gsub gsub!) assert_spell %w(sub! sub gsub!), input: 'ssub!', dictionary: %w(sub sub! gsub gsub!) diff --git a/test/did_you_mean/test_verbose_formatter.rb b/test/did_you_mean/test_verbose_formatter.rb deleted file mode 100644 index 411f175180..0000000000 --- a/test/did_you_mean/test_verbose_formatter.rb +++ /dev/null @@ -1,38 +0,0 @@ -require_relative './helper' - -class VerboseFormatterTest < Test::Unit::TestCase - class ErrorHighlightDummyFormatter - def message_for(spot) - "" - end - end - - def setup - require_relative File.join(DidYouMean::TestHelper.root, 'verbose') - - DidYouMean.formatter = DidYouMean::VerboseFormatter.new - - if defined?(ErrorHighlight) - @error_highlight_old_formatter = ErrorHighlight.formatter - ErrorHighlight.formatter = ErrorHighlightDummyFormatter.new - end - end - - def teardown - DidYouMean.formatter = DidYouMean::PlainFormatter.new - - if defined?(ErrorHighlight) - ErrorHighlight.formatter = @error_highlight_old_formatter - end - end - - def test_message - @error = assert_raise(NoMethodError){ 1.zeor? } - - assert_match <<~MESSAGE.strip, @error.message - undefined method `zeor?' for 1:Integer - - Did you mean? zero? - MESSAGE - end -end