mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Sync did_you_mean again
This commit is contained in:
parent
22249bbb37
commit
66df18c55e
10 changed files with 103 additions and 134 deletions
|
@ -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/key_error_checker'
|
||||||
require_relative 'did_you_mean/spell_checkers/null_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/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'
|
require_relative 'did_you_mean/tree_spell_checker'
|
||||||
|
|
||||||
# The +DidYouMean+ gem adds functionality to suggest possible method/class
|
# The +DidYouMean+ gem adds functionality to suggest possible method/class
|
||||||
|
@ -97,16 +98,17 @@ module DidYouMean
|
||||||
correct_error KeyError, KeyErrorChecker
|
correct_error KeyError, KeyErrorChecker
|
||||||
correct_error NoMethodError, MethodNameChecker
|
correct_error NoMethodError, MethodNameChecker
|
||||||
correct_error LoadError, RequirePathChecker if RUBY_VERSION >= '2.8.0'
|
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+.
|
# Returns the currently set formatter. By default, it is set to +DidYouMean::Formatter+.
|
||||||
def self.formatter
|
def self.formatter
|
||||||
@@formatter
|
@formatter
|
||||||
end
|
end
|
||||||
|
|
||||||
# Updates the primary formatter used to format the suggestions.
|
# Updates the primary formatter used to format the suggestions.
|
||||||
def self.formatter=(formatter)
|
def self.formatter=(*)
|
||||||
@@formatter = formatter
|
@formatter = formatter
|
||||||
end
|
end
|
||||||
|
|
||||||
self.formatter = PlainFormatter.new
|
@formatter = Formatter.new
|
||||||
end
|
end
|
||||||
|
|
35
lib/did_you_mean/formatter.rb
Normal file
35
lib/did_you_mean/formatter.rb
Normal file
|
@ -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
|
|
@ -1,33 +1,4 @@
|
||||||
# frozen-string-literal: true
|
require_relative '../formatter'
|
||||||
|
|
||||||
module DidYouMean
|
warn "`require 'did_you_mean/formatters/plain_formatter'` is deprecated. Please `require 'did_you_mean/formatter'` " \
|
||||||
# The +DidYouMean::PlainFormatter+ is the basic, default formatter for the
|
"instead."
|
||||||
# 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
|
|
||||||
|
|
|
@ -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
|
# frozen-string-literal: true
|
||||||
|
|
||||||
module DidYouMean
|
module DidYouMean
|
||||||
# The +DidYouMean::VerboseFormatter+ uses extra empty lines to make the
|
# For compatibility:
|
||||||
# suggestion stand out more in the error message.
|
VerboseFormatter = Formatter
|
||||||
#
|
|
||||||
# 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
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,25 +10,25 @@ module DidYouMean
|
||||||
end
|
end
|
||||||
|
|
||||||
def correct(input)
|
def correct(input)
|
||||||
input = normalize(input)
|
normalized_input = normalize(input)
|
||||||
threshold = input.length > 3 ? 0.834 : 0.77
|
threshold = normalized_input.length > 3 ? 0.834 : 0.77
|
||||||
|
|
||||||
words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), input) >= threshold }
|
words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), normalized_input) >= threshold }
|
||||||
words.reject! { |word| input == word.to_s }
|
words.reject! { |word| input.to_s == word.to_s }
|
||||||
words.sort_by! { |word| JaroWinkler.distance(word.to_s, input) }
|
words.sort_by! { |word| JaroWinkler.distance(word.to_s, normalized_input) }
|
||||||
words.reverse!
|
words.reverse!
|
||||||
|
|
||||||
# Correct mistypes
|
# Correct mistypes
|
||||||
threshold = (input.length * 0.25).ceil
|
threshold = (normalized_input.length * 0.25).ceil
|
||||||
corrections = words.select { |c| Levenshtein.distance(normalize(c), input) <= threshold }
|
corrections = words.select { |c| Levenshtein.distance(normalize(c), normalized_input) <= threshold }
|
||||||
|
|
||||||
# Correct misspells
|
# Correct misspells
|
||||||
if corrections.empty?
|
if corrections.empty?
|
||||||
corrections = words.select do |word|
|
corrections = words.select do |word|
|
||||||
word = normalize(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.first(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
20
lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb
Normal file
20
lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb
Normal file
|
@ -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
|
|
@ -1,4 +1,2 @@
|
||||||
require_relative '../did_you_mean'
|
warn "The verbose formatter has been removed and now `require 'did_you_mean/verbose'` has no effect. Please " \
|
||||||
require_relative 'formatters/verbose_formatter'
|
"remove this call."
|
||||||
|
|
||||||
DidYouMean.formatter = DidYouMean::VerboseFormatter.new
|
|
||||||
|
|
|
@ -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
|
|
@ -10,6 +10,7 @@ class SpellCheckerTest < Test::Unit::TestCase
|
||||||
assert_spell 'eval', input: 'veal', dictionary: ['email', 'fail', 'eval']
|
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 '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(gsub! gsub), input: 'gsuv!', dictionary: %w(sub gsub gsub!)
|
||||||
assert_spell %w(sub! sub gsub!), input: 'ssub!', dictionary: %w(sub sub! gsub gsub!)
|
assert_spell %w(sub! sub gsub!), input: 'ssub!', dictionary: %w(sub sub! gsub gsub!)
|
||||||
|
|
|
@ -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
|
|
Loading…
Reference in a new issue