diff options
Diffstat (limited to 'lib/did_you_mean')
-rw-r--r-- | lib/did_you_mean/core_ext/name_error.rb | 57 | ||||
-rw-r--r-- | lib/did_you_mean/formatter.rb | 44 | ||||
-rw-r--r-- | lib/did_you_mean/formatters/plain_formatter.rb | 35 | ||||
-rw-r--r-- | lib/did_you_mean/formatters/verbose_formatter.rb | 49 | ||||
-rw-r--r-- | lib/did_you_mean/jaro_winkler.rb | 7 | ||||
-rw-r--r-- | lib/did_you_mean/spell_checker.rb | 18 | ||||
-rw-r--r-- | lib/did_you_mean/spell_checkers/key_error_checker.rb | 10 | ||||
-rw-r--r-- | lib/did_you_mean/spell_checkers/method_name_checker.rb | 10 | ||||
-rw-r--r-- | lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb | 5 | ||||
-rw-r--r-- | lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb | 28 | ||||
-rw-r--r-- | lib/did_you_mean/spell_checkers/require_path_checker.rb | 5 | ||||
-rw-r--r-- | lib/did_you_mean/verbose.rb | 6 | ||||
-rw-r--r-- | lib/did_you_mean/version.rb | 2 |
13 files changed, 162 insertions, 114 deletions
diff --git a/lib/did_you_mean/core_ext/name_error.rb b/lib/did_you_mean/core_ext/name_error.rb index 7e99282b41..8c170c4b90 100644 --- a/lib/did_you_mean/core_ext/name_error.rb +++ b/lib/did_you_mean/core_ext/name_error.rb @@ -1,24 +1,49 @@ module DidYouMean module Correctable - SKIP_TO_S_FOR_SUPER_LOOKUP = true - private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP + if Exception.method_defined?(:detailed_message) + # just for compatibility + def original_message + # we cannot use alias here because + to_s + end + + def detailed_message(highlight: true, did_you_mean: true, **) + msg = super.dup + + return msg unless did_you_mean + + suggestion = DidYouMean.formatter.message_for(corrections) + + if highlight + suggestion = suggestion.gsub(/.+/) { "\e[1m" + $& + "\e[m" } + end - def original_message - meth = method(:to_s) - while meth.owner.const_defined?(:SKIP_TO_S_FOR_SUPER_LOOKUP) - meth = meth.super_method + msg << suggestion + msg + rescue + super + end + else + SKIP_TO_S_FOR_SUPER_LOOKUP = true + private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP + + def original_message + meth = method(:to_s) + while meth.owner.const_defined?(:SKIP_TO_S_FOR_SUPER_LOOKUP) + meth = meth.super_method + end + meth.call end - meth.call - end - def to_s - msg = super.dup - suggestion = DidYouMean.formatter.message_for(corrections) + def to_s + msg = super.dup + suggestion = DidYouMean.formatter.message_for(corrections) - msg << suggestion if !msg.include?(suggestion) - msg - rescue - super + msg << suggestion if !msg.include?(suggestion) + msg + rescue + super + end end def corrections @@ -26,7 +51,7 @@ module DidYouMean end def spell_checker - SPELL_CHECKERS[self.class.to_s].new(self) + DidYouMean.spell_checkers[self.class.to_s].new(self) end end end diff --git a/lib/did_you_mean/formatter.rb b/lib/did_you_mean/formatter.rb new file mode 100644 index 0000000000..c43748f707 --- /dev/null +++ b/lib/did_you_mean/formatter.rb @@ -0,0 +1,44 @@ +# 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 self.message_for(corrections) + corrections.empty? ? "" : "\nDid you mean? #{corrections.join("\n ")}" + end + + def message_for(corrections) + warn "The instance method #message_for has been deprecated. Please use the class method " \ + "DidYouMean::Formatter.message_for(...) instead." + + self.class.message_for(corrections) + end + end + + PlainFormatter = Formatter + + deprecate_constant :PlainFormatter +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..f6623681f2 100644 --- a/lib/did_you_mean/formatters/verbose_formatter.rb +++ b/lib/did_you_mean/formatters/verbose_formatter.rb @@ -1,49 +1,10 @@ # 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 +warn "`require 'did_you_mean/formatters/verbose_formatter'` is deprecated and falls back to the default 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::PlainFormatter.new - # - # puts formatter.message_for(["methods", "method"]) - # - # - # Did you mean? methods - # method - # - # # => nil - # - def message_for(corrections) - return "" if corrections.empty? +require_relative '../formatter' - output = "\n\n Did you mean? ".dup - output << corrections.join("\n ") - output << "\n " - end - end +module DidYouMean + # For compatibility: + VerboseFormatter = Formatter end diff --git a/lib/did_you_mean/jaro_winkler.rb b/lib/did_you_mean/jaro_winkler.rb index 56db130af4..9a3e57f6d7 100644 --- a/lib/did_you_mean/jaro_winkler.rb +++ b/lib/did_you_mean/jaro_winkler.rb @@ -8,8 +8,7 @@ module DidYouMean m = 0.0 t = 0.0 - range = (length2 / 2).floor - 1 - range = 0 if range < 0 + range = length2 > 3 ? length2 / 2 - 1 : 0 flags1 = 0 flags2 = 0 @@ -72,10 +71,8 @@ module DidYouMean codepoints2 = str2.codepoints prefix_bonus = 0 - i = 0 str1.each_codepoint do |char1| - char1 == codepoints2[i] && i < 4 ? prefix_bonus += 1 : break - i += 1 + char1 == codepoints2[prefix_bonus] && prefix_bonus < 4 ? prefix_bonus += 1 : break end jaro_distance + (prefix_bonus * WEIGHT * (1 - jaro_distance)) 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/key_error_checker.rb b/lib/did_you_mean/spell_checkers/key_error_checker.rb index be4bea7789..955bff1be6 100644 --- a/lib/did_you_mean/spell_checkers/key_error_checker.rb +++ b/lib/did_you_mean/spell_checkers/key_error_checker.rb @@ -14,7 +14,15 @@ module DidYouMean private def exact_matches - @exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect) + @exact_matches ||= @keys.select { |word| @key == word.to_s }.map { |obj| format_object(obj) } + end + + def format_object(symbol_or_object) + if symbol_or_object.is_a?(Symbol) + ":#{symbol_or_object}" + else + symbol_or_object.to_s + end end end 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 0483127d6f..b5cbbb5da6 100644 --- a/lib/did_you_mean/spell_checkers/method_name_checker.rb +++ b/lib/did_you_mean/spell_checkers/method_name_checker.rb @@ -6,6 +6,7 @@ module DidYouMean NAMES_TO_EXCLUDE = { NilClass => nil.methods } NAMES_TO_EXCLUDE.default = [] + Ractor.make_shareable(NAMES_TO_EXCLUDE) if defined?(Ractor) # +MethodNameChecker::RB_RESERVED_WORDS+ is the list of reserved words in # Ruby that take an argument. Unlike @@ -36,6 +37,8 @@ module DidYouMean yield ) + Ractor.make_shareable(RB_RESERVED_WORDS) if defined?(Ractor) + def initialize(exception) @method_name = exception.name @receiver = exception.receiver @@ -56,6 +59,13 @@ module DidYouMean method_names = receiver.methods + receiver.singleton_methods method_names += receiver.private_methods if @private_call method_names.uniq! + # Assume that people trying to use a writer are not interested in a reader + # and vice versa + if method_name.match?(/=\Z/) + method_names.select! { |name| name.match?(/=\Z/) } + else + method_names.reject! { |name| name.match?(/=\Z/) } + end method_names else [] diff --git a/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb b/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb index 3e51b4fa3a..9a6e04fe64 100644 --- a/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb +++ b/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb @@ -8,6 +8,7 @@ module DidYouMean NAMES_TO_EXCLUDE = { 'foo' => [:fork, :for] } NAMES_TO_EXCLUDE.default = [] + Ractor.make_shareable(NAMES_TO_EXCLUDE) if defined?(Ractor) # +VariableNameChecker::RB_RESERVED_WORDS+ is the list of all reserved # words in Ruby. They could be declared like methods are, and a typo would @@ -62,6 +63,8 @@ module DidYouMean __ENCODING__ ) + Ractor.make_shareable(RB_RESERVED_WORDS) if defined?(Ractor) + def initialize(exception) @name = exception.name.to_s.tr("@", "") @lvar_names = exception.respond_to?(:local_variables) ? exception.local_variables : [] @@ -76,7 +79,7 @@ module DidYouMean def corrections @corrections ||= SpellChecker .new(dictionary: (RB_RESERVED_WORDS + lvar_names + method_names + ivar_names + cvar_names)) - .correct(name) - NAMES_TO_EXCLUDE[@name] + .correct(name).uniq - NAMES_TO_EXCLUDE[@name] end end 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..622d4dee25 --- /dev/null +++ b/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb @@ -0,0 +1,28 @@ +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 { |obj| format_object(obj) } + end + + def format_object(symbol_or_object) + if symbol_or_object.is_a?(Symbol) + ":#{symbol_or_object}" + else + symbol_or_object.to_s + end + end + end +end diff --git a/lib/did_you_mean/spell_checkers/require_path_checker.rb b/lib/did_you_mean/spell_checkers/require_path_checker.rb index e4cdb9f43d..586ced37de 100644 --- a/lib/did_you_mean/spell_checkers/require_path_checker.rb +++ b/lib/did_you_mean/spell_checkers/require_path_checker.rb @@ -9,7 +9,10 @@ module DidYouMean attr_reader :path INITIAL_LOAD_PATH = $LOAD_PATH.dup.freeze - ENV_SPECIFIC_EXT = ".#{RbConfig::CONFIG["DLEXT"]}" + Ractor.make_shareable(INITIAL_LOAD_PATH) if defined?(Ractor) + + ENV_SPECIFIC_EXT = ".#{RbConfig::CONFIG["DLEXT"]}" + Ractor.make_shareable(ENV_SPECIFIC_EXT) if defined?(Ractor) private_constant :INITIAL_LOAD_PATH, :ENV_SPECIFIC_EXT 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/lib/did_you_mean/version.rb b/lib/did_you_mean/version.rb index 8df73e1d62..5745ca1efd 100644 --- a/lib/did_you_mean/version.rb +++ b/lib/did_you_mean/version.rb @@ -1,3 +1,3 @@ module DidYouMean - VERSION = "1.6.0-alpha" + VERSION = "1.6.3".freeze end |