summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorYuki Nishijima <yk.nishijima@gmail.com>2020-05-22 17:17:10 -0400
committerYuki Nishijima <yk.nishijima@gmail.com>2020-06-06 13:02:08 -0400
commite5f54465284b4505042fca10ace998e1d29c2313 (patch)
tree3770bc320b3cb7fdd7e5406bb1fbcd5c4c705c77 /lib
parent0c00a4176ba353d59d8c991428574ef2c2676674 (diff)
downloadruby-e5f54465284b4505042fca10ace998e1d29c2313.tar.gz
Sync did_you_mean
Diffstat (limited to 'lib')
-rw-r--r--lib/did_you_mean.rb4
-rw-r--r--lib/did_you_mean/experimental/ivar_name_correction.rb39
-rw-r--r--lib/did_you_mean/spell_checkers/method_name_checker.rb7
-rw-r--r--lib/did_you_mean/spell_checkers/require_path_checker.rb35
-rw-r--r--lib/did_you_mean/tree_spell_checker.rb150
5 files changed, 119 insertions, 116 deletions
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?
- suggestions
- end
+ plausibles = plausible_dimensions(input)
+ return fall_back_to_normal_spell_check(input) if plausibles.empty?
- private
+ suggestions = find_suggestions(input, plausibles)
+ return fall_back_to_normal_spell_check(input) if suggestions.empty?
- def parse_dimensions
- ParseDimensions.new(dictionary, separator).call
+ suggestions
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
+ def dictionary_without_leaves
+ @dictionary_without_leaves ||= dictionary.map { |word| word.split(separator)[0..-2] }.uniq
end
- def no_idea(input)
- return [] unless augment
- ::DidYouMean::SpellChecker.new(dictionary: dictionary).correct(input)
+ def tree_depth
+ @tree_depth ||= dictionary_without_leaves.max { |a, b| a.size <=> b.size }.size
end
- def find_ideas(paths, leaf)
- paths.map do |path|
- names = find_leaves(path)
- ideas = CorrectElement.new.call names, leaf
- ideas_to_paths ideas, leaf, names, path
- end
+ def dimensions
+ @dimensions ||= tree_depth.times.map do |index|
+ dictionary_without_leaves.map { |element| element[index] }.compact.uniq
+ end
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 }
+ 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 find_leaves(path)
- dictionary.map do |str|
- next unless str.include? "#{path}#{separator}"
- str.gsub("#{path}#{separator}", '')
- end.compact
+ 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 do |state|
- state.join separator
- end
+ states.map { |state| state.join(separator) }
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
+ private
- # parses the elements in each dimension
- class ParseDimensions
- def initialize(dictionary, separator)
- @dictionary = dictionary
- @separator = separator
+ def find_suggestions(input, plausibles)
+ states = plausibles[0].product(*plausibles[1..-1])
+ paths = possible_paths(states)
+ leaf = input.split(separator).last
+
+ find_ideas(paths, leaf)
end
- def call
- leafless = remove_leaves
- dimensions = find_elements leafless
- dimensions.map do |elements|
- elements.to_set.to_a
- end
+ def fall_back_to_normal_spell_check(input)
+ return [] unless augment
+
+ ::DidYouMean::SpellChecker.new(dictionary: dictionary).correct(input)
end
- private
+ def find_ideas(paths, leaf)
+ paths.flat_map do |path|
+ names = find_leaves(path)
+ ideas = correct_element(names, leaf)
- def remove_leaves
- dictionary.map do |a|
- elements = a.split(separator)
- elements[0..-2]
- end.to_set.to_a
+ ideas_to_paths(ideas, leaf, names, path)
+ end.compact
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
+ def ideas_to_paths(ideas, leaf, names, path)
+ if ideas.empty?
+ nil
+ elsif names.include?(leaf)
+ ["#{path}#{separator}#{leaf}"]
+ else
+ ideas.map {|str| "#{path}#{separator}#{str}" }
end
- dimensions
end
- attr_reader :dictionary, :separator
- end
+ def correct_element(names, element)
+ return names if names.size == 1
- # identifies the elements close to element
- class CorrectElement
- def initialize
- end
+ str = normalize(element)
- def call(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)
- end
+ return [str] if names.include?(str)
- private
+ ::DidYouMean::SpellChecker.new(dictionary: names).correct(str)
+ end
- 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