diff options
author | Kevin Deisz <kevin.deisz@gmail.com> | 2019-10-29 10:08:37 -0400 |
---|---|---|
committer | Yuki Nishijima <yk.nishijima@gmail.com> | 2019-11-30 21:08:19 -0500 |
commit | 171803d5d34feb1b4244ca81b9db0a7bc2171c85 (patch) | |
tree | 664ee644da144f28152097fbe5ea43329bfc0576 /lib/did_you_mean/spell_checkers | |
parent | a2fc6a51dd2e1a153559038795e1e2509f9c6a94 (diff) |
Promote did_you_mean to default gem
At the moment, there are some problems with regard to bundler + did_you_mean because of did_you_mean being a bundled gem. Since the vendored version of thor inside bundler and ruby itself explicitly requires did_you_mean, it can become difficult to load it when using Bundler.setup. See this issue: https://github.com/yuki24/did_you_mean/issues/117#issuecomment-482733159 for more details.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/2689
Diffstat (limited to 'lib/did_you_mean/spell_checkers')
6 files changed, 234 insertions, 0 deletions
diff --git a/lib/did_you_mean/spell_checkers/key_error_checker.rb b/lib/did_you_mean/spell_checkers/key_error_checker.rb new file mode 100644 index 0000000000..be4bea7789 --- /dev/null +++ b/lib/did_you_mean/spell_checkers/key_error_checker.rb @@ -0,0 +1,20 @@ +require_relative "../spell_checker" + +module DidYouMean + class KeyErrorChecker + def initialize(key_error) + @key = key_error.key + @keys = key_error.receiver.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/spell_checkers/method_name_checker.rb b/lib/did_you_mean/spell_checkers/method_name_checker.rb new file mode 100644 index 0000000000..3ca8a37e08 --- /dev/null +++ b/lib/did_you_mean/spell_checkers/method_name_checker.rb @@ -0,0 +1,56 @@ +require_relative "../spell_checker" + +module DidYouMean + class MethodNameChecker + attr_reader :method_name, :receiver + + NAMES_TO_EXCLUDE = { NilClass => nil.methods } + NAMES_TO_EXCLUDE.default = [] + + # +MethodNameChecker::RB_RESERVED_WORDS+ is the list of reserved words in + # Ruby that take an argument. Unlike + # +VariableNameChecker::RB_RESERVED_WORDS+, these reserved words require + # an argument, and a +NoMethodError+ is raised due to the presence of the + # argument. + # + # The +MethodNameChecker+ will use this list to suggest a reversed word if + # a +NoMethodError+ is raised and found closest matches. + # + # Also see +VariableNameChecker::RB_RESERVED_WORDS+. + RB_RESERVED_WORDS = %i( + alias + case + def + defined? + elsif + end + ensure + for + rescue + super + undef + unless + until + when + while + yield + ) + + def initialize(exception) + @method_name = exception.name + @receiver = exception.receiver + @private_call = exception.respond_to?(:private_call?) ? exception.private_call? : false + end + + def corrections + @corrections ||= SpellChecker.new(dictionary: RB_RESERVED_WORDS + method_names).correct(method_name) - NAMES_TO_EXCLUDE[@receiver.class] + end + + def method_names + method_names = receiver.methods + receiver.singleton_methods + method_names += receiver.private_methods if @private_call + method_names.uniq! + method_names + end + end +end diff --git a/lib/did_you_mean/spell_checkers/name_error_checkers.rb b/lib/did_you_mean/spell_checkers/name_error_checkers.rb new file mode 100644 index 0000000000..6e2aaa4cb1 --- /dev/null +++ b/lib/did_you_mean/spell_checkers/name_error_checkers.rb @@ -0,0 +1,20 @@ +require_relative 'name_error_checkers/class_name_checker' +require_relative 'name_error_checkers/variable_name_checker' + +module DidYouMean + class << (NameErrorCheckers = Object.new) + def new(exception) + case exception.original_message + when /uninitialized constant/ + ClassNameChecker + when /undefined local variable or method/, + /undefined method/, + /uninitialized class variable/, + /no member '.*' in struct/ + VariableNameChecker + else + NullChecker + end.new(exception) + end + end +end diff --git a/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb b/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb new file mode 100644 index 0000000000..3bd048b27c --- /dev/null +++ b/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb @@ -0,0 +1,50 @@ +# frozen-string-literal: true + +require 'delegate' +require_relative "../../spell_checker" + +module DidYouMean + class ClassNameChecker + attr_reader :class_name + + def initialize(exception) + @class_name, @receiver, @original_message = exception.name, exception.receiver, exception.original_message + end + + def corrections + @corrections ||= SpellChecker.new(dictionary: class_names) + .correct(class_name) + .map(&:full_name) + .reject {|qualified_name| @original_message.include?(qualified_name) } + end + + def class_names + scopes.flat_map do |scope| + scope.constants.map do |c| + ClassName.new(c, scope == Object ? "" : "#{scope}::") + end + end + end + + def scopes + @scopes ||= @receiver.to_s.split("::").inject([Object]) do |_scopes, scope| + _scopes << _scopes.last.const_get(scope) + end.uniq + end + + class ClassName < SimpleDelegator + attr :namespace + + def initialize(name, namespace = '') + super(name) + @namespace = namespace + end + + def full_name + self.class.new("#{namespace}#{__getobj__}") + end + end + + private_constant :ClassName + end +end 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 new file mode 100644 index 0000000000..3e51b4fa3a --- /dev/null +++ b/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb @@ -0,0 +1,82 @@ +# frozen-string-literal: true + +require_relative "../../spell_checker" + +module DidYouMean + class VariableNameChecker + attr_reader :name, :method_names, :lvar_names, :ivar_names, :cvar_names + + NAMES_TO_EXCLUDE = { 'foo' => [:fork, :for] } + NAMES_TO_EXCLUDE.default = [] + + # +VariableNameChecker::RB_RESERVED_WORDS+ is the list of all reserved + # words in Ruby. They could be declared like methods are, and a typo would + # cause Ruby to raise a +NameError+ because of the way they are declared. + # + # The +:VariableNameChecker+ will use this list to suggest a reversed word + # if a +NameError+ is raised and found closest matches, excluding: + # + # * +do+ + # * +if+ + # * +in+ + # * +or+ + # + # Also see +MethodNameChecker::RB_RESERVED_WORDS+. + RB_RESERVED_WORDS = %i( + BEGIN + END + alias + and + begin + break + case + class + def + defined? + else + elsif + end + ensure + false + for + module + next + nil + not + redo + rescue + retry + return + self + super + then + true + undef + unless + until + when + while + yield + __LINE__ + __FILE__ + __ENCODING__ + ) + + def initialize(exception) + @name = exception.name.to_s.tr("@", "") + @lvar_names = exception.respond_to?(:local_variables) ? exception.local_variables : [] + receiver = exception.receiver + + @method_names = receiver.methods + receiver.private_methods + @ivar_names = receiver.instance_variables + @cvar_names = receiver.class.class_variables + @cvar_names += receiver.class_variables if receiver.kind_of?(Module) + end + + def corrections + @corrections ||= SpellChecker + .new(dictionary: (RB_RESERVED_WORDS + lvar_names + method_names + ivar_names + cvar_names)) + .correct(name) - NAMES_TO_EXCLUDE[@name] + end + end +end diff --git a/lib/did_you_mean/spell_checkers/null_checker.rb b/lib/did_you_mean/spell_checkers/null_checker.rb new file mode 100644 index 0000000000..1306f69d4a --- /dev/null +++ b/lib/did_you_mean/spell_checkers/null_checker.rb @@ -0,0 +1,6 @@ +module DidYouMean + class NullChecker + def initialize(*); end + def corrections; [] end + end +end |