-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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: ruby/did_you_mean#117 (comment) for more details.
- Loading branch information
Showing
42 changed files
with
2,074 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
require_relative "did_you_mean/version" | ||
require_relative "did_you_mean/core_ext/name_error" | ||
|
||
require_relative "did_you_mean/spell_checker" | ||
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/formatters/plain_formatter' | ||
require_relative 'did_you_mean/tree_spell_checker' | ||
|
||
# The +DidYouMean+ gem adds functionality to suggest possible method/class | ||
# names upon errors such as +NameError+ and +NoMethodError+. In Ruby 2.3 or | ||
# later, it is automatically activated during startup. | ||
# | ||
# @example | ||
# | ||
# methosd | ||
# # => NameError: undefined local variable or method `methosd' for main:Object | ||
# # Did you mean? methods | ||
# # method | ||
# | ||
# OBject | ||
# # => NameError: uninitialized constant OBject | ||
# # Did you mean? Object | ||
# | ||
# @full_name = "Yuki Nishijima" | ||
# first_name, last_name = full_name.split(" ") | ||
# # => NameError: undefined local variable or method `full_name' for main:Object | ||
# # Did you mean? @full_name | ||
# | ||
# @@full_name = "Yuki Nishijima" | ||
# @@full_anme | ||
# # => NameError: uninitialized class variable @@full_anme in Object | ||
# # Did you mean? @@full_name | ||
# | ||
# full_name = "Yuki Nishijima" | ||
# full_name.starts_with?("Y") | ||
# # => NoMethodError: undefined method `starts_with?' for "Yuki Nishijima":String | ||
# # Did you mean? start_with? | ||
# | ||
# hash = {foo: 1, bar: 2, baz: 3} | ||
# hash.fetch(:fooo) | ||
# # => KeyError: key not found: :fooo | ||
# # Did you mean? :foo | ||
# | ||
# | ||
# == Disabling +did_you_mean+ | ||
# | ||
# Occasionally, you may want to disable the +did_you_mean+ gem for e.g. | ||
# debugging issues in the error object itself. You can disable it entirely by | ||
# specifying +--disable-did_you_mean+ option to the +ruby+ command: | ||
# | ||
# $ ruby --disable-did_you_mean -e "1.zeor?" | ||
# -e:1:in `<main>': undefined method `zeor?' for 1:Integer (NameError) | ||
# | ||
# When you do not have direct access to the +ruby+ command (e.g. | ||
# +rails console+, +irb+), you could applyoptions using the +RUBYOPT+ | ||
# environment variable: | ||
# | ||
# $ RUBYOPT='--disable-did_you_mean' irb | ||
# irb:0> 1.zeor? | ||
# # => NoMethodError (undefined method `zeor?' for 1:Integer) | ||
# | ||
# | ||
# == Getting the original error message | ||
# | ||
# Sometimes, you do not want to disable the gem entirely, but need to get the | ||
# original error message without suggestions (e.g. testing). In this case, you | ||
# could use the +#original_message+ method on the error object: | ||
# | ||
# no_method_error = begin | ||
# 1.zeor? | ||
# rescue NoMethodError => error | ||
# error | ||
# end | ||
# | ||
# no_method_error.message | ||
# # => NoMethodError (undefined method `zeor?' for 1:Integer) | ||
# # Did you mean? zero? | ||
# | ||
# no_method_error.original_message | ||
# # => NoMethodError (undefined method `zeor?' for 1:Integer) | ||
# | ||
module DidYouMean | ||
# Map of error types and spell checker objects. | ||
SPELL_CHECKERS = Hash.new(NullChecker) | ||
|
||
# Adds +DidYouMean+ functionality to an error using a given spell checker | ||
def self.correct_error(error_class, spell_checker) | ||
SPELL_CHECKERS[error_class.name] = spell_checker | ||
error_class.prepend(Correctable) unless error_class < Correctable | ||
end | ||
|
||
correct_error NameError, NameErrorCheckers | ||
correct_error KeyError, KeyErrorChecker | ||
correct_error NoMethodError, MethodNameChecker | ||
|
||
# Returns the currenctly set formatter. By default, it is set to +DidYouMean::Formatter+. | ||
def self.formatter | ||
@@formatter | ||
end | ||
|
||
# Updates the primary formatter used to format the suggestions. | ||
def self.formatter=(formatter) | ||
@@formatter = formatter | ||
end | ||
|
||
self.formatter = PlainFormatter.new | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
module DidYouMean | ||
module Correctable | ||
def original_message | ||
method(:to_s).super_method.call | ||
end | ||
|
||
def to_s | ||
msg = super.dup | ||
suggestion = DidYouMean.formatter.message_for(corrections) | ||
|
||
msg << suggestion if !msg.end_with?(suggestion) | ||
msg | ||
rescue | ||
super | ||
end | ||
|
||
def corrections | ||
@corrections ||= spell_checker.corrections | ||
end | ||
|
||
def spell_checker | ||
SPELL_CHECKERS[self.class.to_s].new(self) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# coding: utf-8 | ||
lib = File.expand_path('../lib', __FILE__) | ||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) | ||
require 'did_you_mean/version' | ||
|
||
Gem::Specification.new do |spec| | ||
spec.name = "did_you_mean" | ||
spec.version = DidYouMean::VERSION | ||
spec.authors = ["Yuki Nishijima"] | ||
spec.email = ["[email protected]"] | ||
spec.summary = '"Did you mean?" experience in Ruby' | ||
spec.description = 'The gem that has been saving people from typos since 2014.' | ||
spec.homepage = "https://github.com/ruby/did_you_mean" | ||
spec.license = "MIT" | ||
|
||
spec.files = `git ls-files`.split($/).reject{|path| path.start_with?('evaluation/') } | ||
spec.test_files = spec.files.grep(%r{^(test)/}) | ||
spec.require_paths = ["lib"] | ||
|
||
spec.required_ruby_version = '>= 2.5.0' | ||
|
||
spec.add_development_dependency "rake" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
warn "Experimental features in the did_you_mean gem has been removed " \ | ||
"and `require \"did_you_mean/experimental\"' has no effect." |
20 changes: 20 additions & 0 deletions
20
lib/did_you_mean/experimental/initializer_name_correction.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# frozen-string-literal: true | ||
|
||
require_relative '../levenshtein' | ||
|
||
module DidYouMean | ||
module Experimental | ||
module InitializerNameCorrection | ||
def method_added(name) | ||
super | ||
|
||
distance = Levenshtein.distance(name.to_s, 'initialize') | ||
if distance != 0 && distance <= 2 | ||
warn "warning: #{name} might be misspelled, perhaps you meant initialize?" | ||
end | ||
end | ||
end | ||
|
||
::Class.prepend(InitializerNameCorrection) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# frozen-string-literal: true | ||
|
||
require_relative '../../did_you_mean' | ||
|
||
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: | ||
REPLS = { | ||
"(irb)" => -> { Readline::HISTORY.to_a.last } | ||
} | ||
|
||
TRACE = TracePoint.trace(:raise) do |tp| | ||
e = tp.raised_exception | ||
|
||
if SPELL_CHECKERS.include?(e.class.to_s) && !e.instance_variable_defined?(:@frame_binding) | ||
e.instance_variable_set(:@frame_binding, tp.binding) | ||
end | ||
end | ||
|
||
attr_reader :original_checker | ||
|
||
def initialize(no_method_error, original_checker: ) | ||
@original_checker = original_checker.new(no_method_error) | ||
|
||
@location = no_method_error.backtrace_locations.first | ||
@ivar_names = no_method_error.frame_binding.receiver.instance_variables | ||
|
||
no_method_error.remove_instance_variable(:@frame_binding) | ||
end | ||
|
||
def corrections | ||
original_checker.corrections + ivar_name_corrections | ||
end | ||
|
||
def ivar_name_corrections | ||
@ivar_name_corrections ||= SpellChecker.new(dictionary: @ivar_names).correct(receiver_name.to_s) | ||
end | ||
|
||
private | ||
|
||
def receiver_name | ||
return unless @original_checker.receiver.nil? | ||
|
||
abs_path = @location.absolute_path | ||
lineno = @location.lineno | ||
|
||
/@(\w+)*\.#{@original_checker.method_name}/ =~ line(abs_path, lineno).to_s && $1 | ||
end | ||
|
||
def line(abs_path, lineno) | ||
if REPLS[abs_path] | ||
REPLS[abs_path].call | ||
elsif File.exist?(abs_path) | ||
File.open(abs_path) do |file| | ||
file.detect { file.lineno == lineno } | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
NameError.send(:attr, :frame_binding) | ||
SPELL_CHECKERS['NoMethodError'] = Experimental::IvarNameCheckerBuilder.new(SPELL_CHECKERS['NoMethodError']) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# frozen-string-literal: true | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# 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 | ||
end |
Oops, something went wrong.