[rubygems/rubygems] Cancel `bundle console` deprecation
[ruby.git] / lib / did_you_mean.rb
blobe177665099a3ee594784a5841f3adb5f7b52155f
1 require_relative "did_you_mean/version"
2 require_relative "did_you_mean/core_ext/name_error"
4 require_relative "did_you_mean/spell_checker"
5 require_relative 'did_you_mean/spell_checkers/name_error_checkers'
6 require_relative 'did_you_mean/spell_checkers/method_name_checker'
7 require_relative 'did_you_mean/spell_checkers/key_error_checker'
8 require_relative 'did_you_mean/spell_checkers/null_checker'
9 require_relative 'did_you_mean/spell_checkers/require_path_checker'
10 require_relative 'did_you_mean/spell_checkers/pattern_key_name_checker'
11 require_relative 'did_you_mean/formatter'
12 require_relative 'did_you_mean/tree_spell_checker'
14 # The +DidYouMean+ gem adds functionality to suggest possible method/class
15 # names upon errors such as +NameError+ and +NoMethodError+. In Ruby 2.3 or
16 # later, it is automatically activated during startup.
18 # @example
20 #   methosd
21 #   # => NameError: undefined local variable or method `methosd' for main:Object
22 #   #   Did you mean?  methods
23 #   #                  method
25 #   OBject
26 #   # => NameError: uninitialized constant OBject
27 #   #    Did you mean?  Object
29 #   @full_name = "Yuki Nishijima"
30 #   first_name, last_name = full_name.split(" ")
31 #   # => NameError: undefined local variable or method `full_name' for main:Object
32 #   #    Did you mean?  @full_name
34 #   @@full_name = "Yuki Nishijima"
35 #   @@full_anme
36 #   # => NameError: uninitialized class variable @@full_anme in Object
37 #   #    Did you mean?  @@full_name
39 #   full_name = "Yuki Nishijima"
40 #   full_name.starts_with?("Y")
41 #   # => NoMethodError: undefined method `starts_with?' for "Yuki Nishijima":String
42 #   #    Did you mean?  start_with?
44 #   hash = {foo: 1, bar: 2, baz: 3}
45 #   hash.fetch(:fooo)
46 #   # => KeyError: key not found: :fooo
47 #   #    Did you mean?  :foo
50 # == Disabling +did_you_mean+
52 # Occasionally, you may want to disable the +did_you_mean+ gem for e.g.
53 # debugging issues in the error object itself. You can disable it entirely by
54 # specifying +--disable-did_you_mean+ option to the +ruby+ command:
56 #   $ ruby --disable-did_you_mean -e "1.zeor?"
57 #   -e:1:in `<main>': undefined method `zeor?' for 1:Integer (NameError)
59 # When you do not have direct access to the +ruby+ command (e.g.
60 # +rails console+, +irb+), you could applyoptions using the +RUBYOPT+
61 # environment variable:
63 #   $ RUBYOPT='--disable-did_you_mean' irb
64 #   irb:0> 1.zeor?
65 #   # => NoMethodError (undefined method `zeor?' for 1:Integer)
68 # == Getting the original error message
70 # Sometimes, you do not want to disable the gem entirely, but need to get the
71 # original error message without suggestions (e.g. testing). In this case, you
72 # could use the +#original_message+ method on the error object:
74 #   no_method_error = begin
75 #                       1.zeor?
76 #                     rescue NoMethodError => error
77 #                       error
78 #                     end
80 #   no_method_error.message
81 #   # => NoMethodError (undefined method `zeor?' for 1:Integer)
82 #   #    Did you mean?  zero?
84 #   no_method_error.original_message
85 #   # => NoMethodError (undefined method `zeor?' for 1:Integer)
87 module DidYouMean
88   # Map of error types and spell checker objects.
89   @spell_checkers = Hash.new(NullChecker)
91   # Returns a sharable hash map of error types and spell checker objects.
92   def self.spell_checkers
93     @spell_checkers
94   end
96   # Adds +DidYouMean+ functionality to an error using a given spell checker
97   def self.correct_error(error_class, spell_checker)
98     if defined?(Ractor)
99       new_mapping = { **@spell_checkers, error_class.to_s => spell_checker }
100       new_mapping.default = NullChecker
102       @spell_checkers = Ractor.make_shareable(new_mapping)
103     else
104       spell_checkers[error_class.to_s] = spell_checker
105     end
107     error_class.prepend(Correctable) if error_class.is_a?(Class) && !(error_class < Correctable)
108   end
110   correct_error NameError, NameErrorCheckers
111   correct_error KeyError, KeyErrorChecker
112   correct_error NoMethodError, MethodNameChecker
113   correct_error LoadError, RequirePathChecker if RUBY_VERSION >= '2.8.0'
114   correct_error NoMatchingPatternKeyError, PatternKeyNameChecker if defined?(::NoMatchingPatternKeyError)
116   # TODO: Remove on the 3.4 development start:
117   class DeprecatedMapping # :nodoc:
118     def []=(key, value)
119       warn "Calling `DidYouMean::SPELL_CHECKERS[#{key.to_s}] = #{value.to_s}' has been deprecated. " \
120            "Please call `DidYouMean.correct_error(#{key.to_s}, #{value.to_s})' instead."
122       DidYouMean.correct_error(key, value)
123     end
125     def merge!(hash)
126       warn "Calling `DidYouMean::SPELL_CHECKERS.merge!(error_name => spell_checker)' has been deprecated. " \
127            "Please call `DidYouMean.correct_error(error_name, spell_checker)' instead."
129       hash.each do |error_class, spell_checker|
130         DidYouMean.correct_error(error_class, spell_checker)
131       end
132     end
133   end
135   # TODO: Remove on the 3.4 development start:
136   SPELL_CHECKERS = DeprecatedMapping.new
137   deprecate_constant :SPELL_CHECKERS
138   private_constant :DeprecatedMapping
140   # Returns the currently set formatter. By default, it is set to +DidYouMean::Formatter+.
141   def self.formatter
142     if defined?(Ractor)
143       Ractor.current[:__did_you_mean_formatter__] || Formatter
144     else
145       Formatter
146     end
147   end
149   # Updates the primary formatter used to format the suggestions.
150   def self.formatter=(formatter)
151     if defined?(Ractor)
152       Ractor.current[:__did_you_mean_formatter__] = formatter
153     end
154   end