diff --git a/stdlib/did_you_mean/0/did_you_mean.rbs b/stdlib/did_you_mean/0/did_you_mean.rbs new file mode 100644 index 000000000..04e81b0ac --- /dev/null +++ b/stdlib/did_you_mean/0/did_you_mean.rbs @@ -0,0 +1,348 @@ +# +# 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 `
': 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 + # + # TODO: Remove on 3.3: + # + SPELL_CHECKERS: untyped + + NameErrorCheckers: Object + + VERSION: String + + class ClassNameChecker + class ClassName < ::String + end + end + + module Correctable + SKIP_TO_S_FOR_SUPER_LOOKUP: true + + # + # + def corrections: () -> Array[String] + end + + # + # 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: (Array[String] corrections) -> String + end + + module JaroWinkler + WEIGHT: Float + + THRESHOLD: Float + + # + # + def self?.distance: (String, String) -> Integer + end + + module Jaro + # + # + def self?.distance: (String, String) -> Integer + end + + class KeyErrorChecker + # + # + def initialize: (KeyError[_ToS, Hash[_ToS, untyped]]) -> void + + # + # + def corrections: () -> Array[String] + end + + module Levenshtein + def self?.distance: (String, String) -> Integer? + end + + class MethodNameChecker + NAMES_TO_EXCLUDE: Hash[untyped, Array[Symbol]] + + # + # `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: Array[Symbol] + + # + # + def initialize: (NoMethodError[untyped] exception) -> void + + # + # + def corrections: () -> Array[Symbol] + end + + class NullChecker + # + # + def initialize: (*untyped) -> void + + # + # + def corrections: () -> Array[untyped] + end + + class PatternKeyNameChecker + # + # + def initialize: (untyped) -> void + + # + # + def corrections: () -> Array[String] + end + + class RequirePathChecker + INITIAL_LOAD_PATH: Array[String] + ENV_SPECIFIC_EXT: String + + # + # + def self.requireables: -> Array[String] + + # + # + def initialize: (untyped exception) -> void + + # + # + def corrections: () -> Array[String] + end + + class SpellChecker + # + # + def initialize: (dictionary: Array[String | Symbol]) -> void + + # + # + def correct: (String | Symbol input) -> Array[String] + end + + # + # spell checker for a dictionary that has a tree structure, see + # doc/tree_spell_checker_api.md + # + class TreeSpellChecker + # + # + def initialize: (dictionary: Array[String], ?separator: String, ?augment: bool?) -> void + + # + # + def correct: (String input) -> Array[String] + end + + class VariableNameChecker + NAMES_TO_EXCLUDE: Hash[String, Array[Symbol]] + + # + # `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: Array[Symbol] + + # + # + def initialize: (NameError[untyped]) -> void + + # + # + def corrections: () -> Array[Symbol] + end +end + +%a{annotate:rdoc:skip} +class NameError[T] + prepend DidYouMean::Correctable +end + +%a{annotate:rdoc:skip} +class KeyError[K, R] + prepend DidYouMean::Correctable +end + +%a{annotate:rdoc:skip} +class LoadError + prepend DidYouMean::Correctable +end diff --git a/test/stdlib/did_you_mean/DidYouMean_test.rb b/test/stdlib/did_you_mean/DidYouMean_test.rb new file mode 100644 index 000000000..ef36094b3 --- /dev/null +++ b/test/stdlib/did_you_mean/DidYouMean_test.rb @@ -0,0 +1,303 @@ +require_relative "../test_helper" + +class DidYouMean::CorrectableTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::Correctable" + + def test_corrections + c = Class.new.include(DidYouMean::Correctable) + assert_send_type "() -> ::Array[::String]", + c.new, :corrections + end +end + +class DidYouMean::FormatterSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::Formatter)" + + def test_message_for + assert_send_type "(::Array[::String] corrections) -> ::String", + DidYouMean::Formatter, :message_for, ['foo', 'bar', 'baz'] + end +end + +class DidYouMean::JaroSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::Jaro)" + + def test_distance + assert_send_type "(::String, ::String) -> ::Integer", + DidYouMean::Jaro, :distance, "foo", "bar" + end +end + +class DidYouMean::JaroTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::Jaro" + + def test_distance + c = Class.new.include(DidYouMean::Jaro) + assert_send_type "(::String, ::String) -> ::Integer", + c.new, :distance, "foo", "bar" + end +end + +class DidYouMean::JaroWinklerSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::JaroWinkler)" + + def test_distance + assert_send_type "(::String, ::String) -> ::Integer", + DidYouMean::JaroWinkler, :distance, "foo", "bar" + end +end + +class DidYouMean::JaroWinklerTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::JaroWinkler" + + def test_distance + c = Class.new.include(DidYouMean::JaroWinkler) + assert_send_type "(::String, ::String) -> ::Integer", + c.new, :distance, "foo", "bar" + end +end + +class DidYouMean::KeyErrorCheckerSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::KeyErrorChecker)" + + def test_initialize + assert_send_type "(::KeyError[Symbol, Hash[Symbol, Integer]]) -> void", + DidYouMean::KeyErrorChecker, :new, KeyError.new(key: :a, receiver: { b: 1 }) + end +end + +class DidYouMean::KeyErrorCheckerTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::KeyErrorChecker" + + def test_corrections + assert_send_type "() -> ::Array[::String]", + DidYouMean::KeyErrorChecker.new(KeyError.new(key: :a, receiver: { b: 1 })), :corrections + end +end + +class DidYouMean::LevenshteinSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::Levenshtein)" + + def test_distance + assert_send_type "(::String, ::String) -> ::Integer?", + DidYouMean::Levenshtein, :distance, "foo", "bar" + end +end + +class DidYouMean::LevenshteinTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::Levenshtein" + + def test_distance + c = Class.new.include(DidYouMean::Levenshtein) + assert_send_type "(::String, ::String) -> ::Integer?", + c.new, :distance, "foo", "bar" + end +end + +class DidYouMean::MethodNameCheckerSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::MethodNameChecker)" + + def test_new + assert_send_type "(::NoMethodError[nil]) -> ::DidYouMean::MethodNameChecker", + DidYouMean::MethodNameChecker, :new, NoMethodError.new(receiver: nil) + end +end + +class DidYouMean::MethodNameCheckerTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::MethodNameChecker" + + def test_corrections + assert_send_type "() -> ::Array[::Symbol]", + DidYouMean::MethodNameChecker.new(NoMethodError.new("error", :object, receiver: Object.new)), :corrections + end +end + +class DidYouMean::NullCheckerSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::NullChecker)" + + def test_new + assert_send_type "(*untyped) -> ::DidYouMean::NullChecker", + DidYouMean::NullChecker, :new + end +end + +class DidYouMean::NullCheckerTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::NullChecker" + + def test_corrections + assert_send_type "() -> ::Array[untyped]", + DidYouMean::NullChecker.new, :corrections + end +end + +class DidYouMean::PatternKeyNameCheckerSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::PatternKeyNameChecker)" + + def test_new + mock = Struct.new(:key, :matchee).new(:foo, { fooo: 1 }) + assert_send_type "(untyped) -> ::DidYouMean::PatternKeyNameChecker", + DidYouMean::PatternKeyNameChecker, :new, mock + end +end + +class DidYouMean::PatternKeyNameCheckerTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::PatternKeyNameChecker" + + def test_corrections + mock = Struct.new(:key, :matchee).new(:foo, { fooo: 1 }) + assert_send_type "() -> ::Array[::String]", + DidYouMean::PatternKeyNameChecker.new(mock), :corrections + end +end + +class DidYouMean::RequirePathCheckerSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::RequirePathChecker)" + + def test_new + mock = Struct.new(:path).new("did_you_meen") + assert_send_type "(untyped exception) -> ::DidYouMean::RequirePathChecker", + DidYouMean::RequirePathChecker, :new, mock + end + + def test_requireables + assert_send_type "() -> ::Array[::String]", + DidYouMean::RequirePathChecker, :requireables + end +end + +class DidYouMean::RequirePathCheckerTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::RequirePathChecker" + + def test_corrections + mock = Struct.new(:path).new("did_you_meen") + assert_send_type "() -> ::Array[::String]", + DidYouMean::RequirePathChecker.new(mock), :corrections + end +end + +class DidYouMean::SpellCheckerSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::SpellChecker)" + + def test_new + assert_send_type "(dictionary: ::Array[::String | ::Symbol]) -> ::DidYouMean::SpellChecker", + DidYouMean::SpellChecker, :new, dictionary: ["foo"] + end +end + +class DidYouMean::SpellCheckerTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::SpellChecker" + + def test_correct + assert_send_type "(::String | ::Symbol input) -> ::Array[::String]", + DidYouMean::SpellChecker.new(dictionary: ["foo"]), :correct, "fooo" + end +end + +class DidYouMean::TreeSpellCheckerSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::TreeSpellChecker)" + + def test_new + assert_send_type "(dictionary: ::Array[::String], ?separator: ::String, ?augment: bool?) -> ::DidYouMean::TreeSpellChecker", + DidYouMean::TreeSpellChecker, :new, dictionary: ["foo/bar"] + end +end + +class DidYouMean::TreeSpellCheckerTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::TreeSpellChecker" + + def test_correct + assert_send_type "(::String input) -> ::Array[::String]", + DidYouMean::TreeSpellChecker.new(dictionary: ["foo/bar"]), :correct, "foo/baz" + end +end + +class DidYouMean::VariableNameCheckerSingletonTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "singleton(::DidYouMean::VariableNameChecker)" + + def test_new + assert_send_type "(NameError[untyped]) -> void", + DidYouMean::VariableNameChecker, :new, NameError.new(receiver: Object.new) + end +end + +class DidYouMean::VariableNameCheckerTest < Test::Unit::TestCase + include TypeAssertions + + library "did_you_mean" + testing "::DidYouMean::VariableNameChecker" + + def test_corrections + mock = Struct.new(:name, :receiver).new(:object, Object.new) + assert_send_type "() -> ::Array[::Symbol]", + DidYouMean::VariableNameChecker.new(mock), :corrections + end +end