did_you_mean 0.10.0 → 1.0.0.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +0 -18
- data/CHANGELOG.md +28 -3
- data/Gemfile +0 -7
- data/README.md +2 -16
- data/Rakefile +1 -13
- data/benchmark/memory_usage.rb +4 -4
- data/did_you_mean.gemspec +2 -12
- data/evaluation/calculator.rb +5 -5
- data/lib/did_you_mean.rb +18 -26
- data/lib/did_you_mean/core_ext/name_error.rb +15 -25
- data/lib/did_you_mean/formatter.rb +5 -11
- data/lib/did_you_mean/jaro_winkler.rb +3 -4
- data/lib/did_you_mean/levenshtein.rb +1 -1
- data/lib/did_you_mean/{finders.rb → spell_checkable.rb} +4 -12
- data/lib/did_you_mean/spell_checkers/method_name_checker.rb +54 -0
- data/lib/did_you_mean/{finders/name_error_finders.rb → spell_checkers/name_error_checkers.rb} +7 -7
- data/lib/did_you_mean/{finders/name_error_finders/class_finder.rb → spell_checkers/name_error_checkers/class_name_checker.rb} +8 -26
- data/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb +20 -0
- data/lib/did_you_mean/spell_checkers/null_checker.rb +6 -0
- data/lib/did_you_mean/version.rb +1 -1
- data/test/core_ext/name_error_extension_test.rb +20 -23
- data/test/correctable/class_name_test.rb +14 -14
- data/test/correctable/method_name_test.rb +21 -21
- data/test/correctable/variable_name_test.rb +30 -25
- data/test/{word_collection_test.rb → spell_checker_test.rb} +20 -20
- data/test/test_helper.rb +5 -8
- metadata +15 -50
- data/ext/did_you_mean/extconf.rb +0 -2
- data/ext/did_you_mean/method_receiver.c +0 -20
- data/lib/did_you_mean/core_ext/no_method_error.rb +0 -46
- data/lib/did_you_mean/core_ext/rubinius.rb +0 -16
- data/lib/did_you_mean/finders/method_finder.rb +0 -62
- data/lib/did_you_mean/finders/name_error_finders/name_finder.rb +0 -18
- data/lib/did_you_mean/test_helper.rb +0 -7
- data/test/core_ext/no_method_error_extension_test.rb +0 -38
@@ -0,0 +1,54 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class MethodNameChecker
|
3
|
+
include SpellCheckable
|
4
|
+
attr_reader :method_name, :receiver
|
5
|
+
|
6
|
+
REPLS = {
|
7
|
+
"(irb)" => -> { Readline::HISTORY.to_a.last }
|
8
|
+
}
|
9
|
+
|
10
|
+
def initialize(exception)
|
11
|
+
@method_name = exception.name
|
12
|
+
@receiver = exception.receiver
|
13
|
+
@binding = exception.frame_binding
|
14
|
+
@location = exception.backtrace_locations.first
|
15
|
+
@ivar_names = VariableNameChecker.new(exception).ivar_names
|
16
|
+
end
|
17
|
+
|
18
|
+
def candidates
|
19
|
+
{
|
20
|
+
method_name => method_names,
|
21
|
+
receiver_name.to_s => @ivar_names
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_names
|
26
|
+
method_names = receiver.methods + receiver.singleton_methods
|
27
|
+
method_names += receiver.private_methods if receiver.equal?(@binding.receiver)
|
28
|
+
method_names.delete(method_name)
|
29
|
+
method_names.uniq!
|
30
|
+
method_names
|
31
|
+
end
|
32
|
+
|
33
|
+
def receiver_name
|
34
|
+
return unless @receiver.nil?
|
35
|
+
|
36
|
+
abs_path = @location.absolute_path
|
37
|
+
lineno = @location.lineno
|
38
|
+
|
39
|
+
/@(\w+)*\.#{@method_name}/ =~ line(abs_path, lineno).to_s && $1
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def line(abs_path, lineno)
|
45
|
+
if REPLS[abs_path]
|
46
|
+
REPLS[abs_path].call
|
47
|
+
elsif File.exist?(abs_path)
|
48
|
+
File.open(abs_path) do |file|
|
49
|
+
file.detect { file.lineno == lineno }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/did_you_mean/{finders/name_error_finders.rb → spell_checkers/name_error_checkers.rb}
RENAMED
@@ -1,5 +1,8 @@
|
|
1
|
+
require 'did_you_mean/spell_checkers/name_error_checkers/class_name_checker'
|
2
|
+
require 'did_you_mean/spell_checkers/name_error_checkers/variable_name_checker'
|
3
|
+
|
1
4
|
module DidYouMean
|
2
|
-
module
|
5
|
+
module NameErrorCheckers
|
3
6
|
def self.included(*)
|
4
7
|
raise "Do not include this module since it overrides Class.new method."
|
5
8
|
end
|
@@ -7,15 +10,12 @@ module DidYouMean
|
|
7
10
|
def self.new(exception)
|
8
11
|
case exception.original_message
|
9
12
|
when /uninitialized constant/
|
10
|
-
|
13
|
+
ClassNameChecker
|
11
14
|
when /undefined local variable or method/, /undefined method/, /uninitialized class variable/
|
12
|
-
|
15
|
+
VariableNameChecker
|
13
16
|
else
|
14
|
-
|
17
|
+
NullChecker
|
15
18
|
end.new(exception)
|
16
19
|
end
|
17
20
|
end
|
18
21
|
end
|
19
|
-
|
20
|
-
require 'did_you_mean/finders/name_error_finders/name_finder'
|
21
|
-
require 'did_you_mean/finders/name_error_finders/class_finder'
|
@@ -1,15 +1,15 @@
|
|
1
1
|
require 'delegate'
|
2
2
|
|
3
3
|
module DidYouMean
|
4
|
-
class
|
5
|
-
include
|
4
|
+
class ClassNameChecker
|
5
|
+
include SpellCheckable
|
6
6
|
attr_reader :class_name, :original_message
|
7
7
|
|
8
8
|
def initialize(exception)
|
9
9
|
@class_name, @original_message = exception.name, exception.original_message
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
12
|
+
def candidates
|
13
13
|
{name_from_message => class_names}
|
14
14
|
end
|
15
15
|
|
@@ -21,29 +21,11 @@ module DidYouMean
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
# input since JRuby 1.7 behaves differently from MRI/Rubinius.
|
27
|
-
#
|
28
|
-
# class Name; end
|
29
|
-
# error = (Name::DoesNotExist rescue $!)
|
30
|
-
#
|
31
|
-
# # on MRI/Rubinius
|
32
|
-
# error.name #=> :DoesNotExist
|
33
|
-
#
|
34
|
-
# # on JRuby <= 1.7
|
35
|
-
# error.name #=> :'Name::DoesNotExist'
|
36
|
-
#
|
37
|
-
def name_from_message
|
38
|
-
/([A-Z]\w*$)/.match(original_message)[0]
|
39
|
-
end
|
40
|
-
else
|
41
|
-
def name_from_message
|
42
|
-
class_name || /([A-Z]\w*$)/.match(original_message)[0]
|
43
|
-
end
|
24
|
+
def name_from_message
|
25
|
+
class_name || /([A-Z]\w*$)/.match(original_message)[0]
|
44
26
|
end
|
45
27
|
|
46
|
-
def
|
28
|
+
def corrections
|
47
29
|
super.map(&:full_name)
|
48
30
|
end
|
49
31
|
|
@@ -56,13 +38,13 @@ module DidYouMean
|
|
56
38
|
private
|
57
39
|
|
58
40
|
def scope_base
|
59
|
-
@scope_base ||= (/(([A-Z]\w*::)*)([A-Z]\w*)$/ =~ original_message ? $1 :
|
41
|
+
@scope_base ||= (/(([A-Z]\w*::)*)([A-Z]\w*)$/ =~ original_message ? $1 : EMPTY).split("::")
|
60
42
|
end
|
61
43
|
|
62
44
|
class ClassName < SimpleDelegator
|
63
45
|
attr :namespace
|
64
46
|
|
65
|
-
def initialize(name, namespace = '')
|
47
|
+
def initialize(name, namespace = ''.freeze)
|
66
48
|
super(name)
|
67
49
|
@namespace = namespace
|
68
50
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class VariableNameChecker
|
3
|
+
include SpellCheckable
|
4
|
+
attr_reader :name, :method_names, :lvar_names, :ivar_names, :cvar_names
|
5
|
+
|
6
|
+
def initialize(exception)
|
7
|
+
@name = exception.name.to_s.tr(AT, EMPTY)
|
8
|
+
@lvar_names = exception.frame_binding.local_variables
|
9
|
+
receiver = exception.frame_binding.receiver
|
10
|
+
|
11
|
+
@method_names = receiver.methods + receiver.private_methods
|
12
|
+
@cvar_names = receiver.class.class_variables
|
13
|
+
@ivar_names = receiver.instance_variables
|
14
|
+
end
|
15
|
+
|
16
|
+
def candidates
|
17
|
+
{ name => (lvar_names + method_names + ivar_names + cvar_names) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/did_you_mean/version.rb
CHANGED
@@ -1,31 +1,30 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class NameErrorExtensionTest < Minitest::Test
|
4
|
-
|
4
|
+
SPELL_CHECKERS = DidYouMean::SPELL_CHECKERS
|
5
|
+
|
6
|
+
class TestSpellChecker
|
5
7
|
def initialize(*); end
|
6
|
-
def
|
8
|
+
def corrections; ["Y U SO SLOW?"]; end
|
7
9
|
end
|
8
10
|
|
9
11
|
def setup
|
10
|
-
@org =
|
11
|
-
DidYouMean.finders[NAME_ERROR.to_s] = TestFinder
|
12
|
+
@org, SPELL_CHECKERS['NameError'] = SPELL_CHECKERS['NameError'], TestSpellChecker
|
12
13
|
|
13
|
-
@error = assert_raises(
|
14
|
+
@error = assert_raises(NameError){ doesnt_exist }
|
14
15
|
end
|
15
16
|
|
16
17
|
def teardown
|
17
|
-
|
18
|
+
SPELL_CHECKERS['NameError'] = @org
|
18
19
|
end
|
19
20
|
|
20
21
|
def test_message_provides_original_message
|
21
|
-
skip if RUBY_ENGINE == 'rbx'
|
22
|
-
|
23
22
|
assert_match "undefined local variable or method", @error.to_s
|
24
23
|
end
|
25
24
|
|
26
25
|
def test_message
|
27
|
-
assert_match "Did you mean?
|
28
|
-
assert_match "Did you mean?
|
26
|
+
assert_match "Did you mean? Y U SO SLOW?", @error.to_s
|
27
|
+
assert_match "Did you mean? Y U SO SLOW?", @error.message
|
29
28
|
end
|
30
29
|
|
31
30
|
def test_to_s_does_not_make_disruptive_changes_to_error_message
|
@@ -38,35 +37,33 @@ class NameErrorExtensionTest < Minitest::Test
|
|
38
37
|
end
|
39
38
|
|
40
39
|
class IgnoreCallersTest < Minitest::Test
|
40
|
+
SPELL_CHECKERS = DidYouMean::SPELL_CHECKERS
|
41
|
+
|
41
42
|
class Boomer
|
42
43
|
def initialize(*)
|
43
|
-
raise Exception, "
|
44
|
+
raise Exception, "spell checker was created when it shouldn't!"
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
48
|
def setup
|
48
|
-
@org =
|
49
|
-
DidYouMean
|
49
|
+
@org, SPELL_CHECKERS['NameError'] = SPELL_CHECKERS['NameError'], Boomer
|
50
|
+
DidYouMean::IGNORED_CALLERS << /( |`)do_not_correct_typo'/
|
50
51
|
|
51
|
-
@error = assert_raises(
|
52
|
+
@error = assert_raises(NameError){ doesnt_exist }
|
52
53
|
end
|
53
54
|
|
54
55
|
def teardown
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
def test_ignore_missing_name
|
59
|
-
assert_nothing_raised { missing_name }
|
56
|
+
SPELL_CHECKERS['NameError'] = @org
|
57
|
+
DidYouMean::IGNORED_CALLERS.clear
|
60
58
|
end
|
61
59
|
|
62
|
-
def
|
63
|
-
assert_nothing_raised {
|
60
|
+
def test_ignore
|
61
|
+
assert_nothing_raised { do_not_correct_typo }
|
64
62
|
end
|
65
63
|
|
66
64
|
private
|
67
65
|
|
68
|
-
def
|
69
|
-
def missing_name; @error.message end
|
66
|
+
def do_not_correct_typo; @error.message end
|
70
67
|
|
71
68
|
def assert_nothing_raised
|
72
69
|
yield
|
@@ -28,38 +28,38 @@ class Book
|
|
28
28
|
end
|
29
29
|
|
30
30
|
class ClassNameTest < Minitest::Test
|
31
|
-
def
|
31
|
+
def test_corrections
|
32
32
|
error = assert_raises(NameError) { ::Bo0k }
|
33
|
-
|
33
|
+
assert_correction "Book", error.corrections
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
36
|
+
def test_corrections_include_case_specific_class_name
|
37
37
|
error = assert_raises(NameError) { ::Acronym }
|
38
|
-
|
38
|
+
assert_correction "ACRONYM", error.corrections
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
41
|
+
def test_corrections_include_top_level_class_name
|
42
42
|
error = assert_raises(NameError) { Project.bo0k }
|
43
|
-
|
43
|
+
assert_correction "Book", error.corrections
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
46
|
+
def test_names_in_corrections_have_namespaces
|
47
47
|
error = assert_raises(NameError) { ::Book::TableofContents }
|
48
|
-
|
48
|
+
assert_correction "Book::TableOfContents", error.corrections
|
49
49
|
end
|
50
50
|
|
51
|
-
def
|
51
|
+
def test_corrections_candidates_for_names_in_upper_level_scopes
|
52
52
|
error = assert_raises(NameError) { Book::Page.tableof_contents }
|
53
|
-
|
53
|
+
assert_correction "Book::TableOfContents", error.corrections
|
54
54
|
end
|
55
55
|
|
56
|
-
def
|
56
|
+
def test_corrections_should_work_from_within_instance_method
|
57
57
|
error = assert_raises(NameError) { ::Book.new.tableof_contents }
|
58
|
-
|
58
|
+
assert_correction "Book::TableOfContents", error.corrections
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
61
|
+
def test_corrections_should_work_from_within_instance_method_on_nested_class
|
62
62
|
error = assert_raises(NameError) { ::Book::Page.new.tableof_contents }
|
63
|
-
|
63
|
+
assert_correction "Book::TableOfContents", error.corrections
|
64
64
|
end
|
65
65
|
end
|
@@ -29,56 +29,56 @@ class MethodNameTest < Minitest::Test
|
|
29
29
|
@user = User.new.extend(UserModule)
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def test_corrections_include_instance_method
|
33
33
|
error = assert_raises(NoMethodError){ @user.flrst_name }
|
34
34
|
|
35
|
-
|
36
|
-
assert_match "Did you mean?
|
35
|
+
assert_correction :first_name, error.corrections
|
36
|
+
assert_match "Did you mean? first_name", error.to_s
|
37
37
|
end
|
38
38
|
|
39
|
-
def
|
39
|
+
def test_corrections_include_private_method
|
40
40
|
error = assert_raises(NoMethodError){ @user.friend }
|
41
41
|
|
42
|
-
|
43
|
-
assert_match "Did you mean?
|
42
|
+
assert_correction :friends, error.corrections
|
43
|
+
assert_match "Did you mean? friends", error.to_s
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
46
|
+
def test_corrections_include_method_from_module
|
47
47
|
error = assert_raises(NoMethodError){ @user.fr0m_module }
|
48
48
|
|
49
|
-
|
50
|
-
assert_match "Did you mean?
|
49
|
+
assert_correction :from_module, error.corrections
|
50
|
+
assert_match "Did you mean? from_module", error.to_s
|
51
51
|
end
|
52
52
|
|
53
|
-
def
|
53
|
+
def test_corrections_include_class_method
|
54
54
|
error = assert_raises(NoMethodError){ User.l0ad }
|
55
55
|
|
56
|
-
|
57
|
-
assert_match "Did you mean?
|
56
|
+
assert_correction :load, error.corrections
|
57
|
+
assert_match "Did you mean? load", error.to_s
|
58
58
|
end
|
59
59
|
|
60
60
|
def test_private_methods_should_not_be_suggested
|
61
61
|
error = assert_raises(NoMethodError){ User.new.the_protected_method }
|
62
|
-
refute_includes error.
|
62
|
+
refute_includes error.corrections, :the_protected_method
|
63
63
|
|
64
64
|
error = assert_raises(NoMethodError){ User.new.the_private_method }
|
65
|
-
refute_includes error.
|
65
|
+
refute_includes error.corrections, :the_private_method
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
68
|
+
def test_corrections_when_private_method_is_called_with_args
|
69
69
|
error = assert_raises(NoMethodError){ @user.call_incorrect_private_method }
|
70
70
|
|
71
|
-
|
72
|
-
assert_match "Did you mean?
|
71
|
+
assert_correction :raise, error.corrections
|
72
|
+
assert_match "Did you mean? raise", error.to_s
|
73
73
|
end
|
74
74
|
|
75
75
|
def test_corrects_incorrect_ivar_name
|
76
|
-
skip if RUBY_ENGINE == 'rbx'
|
77
|
-
|
78
76
|
@number = 1
|
77
|
+
@nubmer = nil
|
79
78
|
error = assert_raises(NoMethodError) { @nubmer.zero? }
|
79
|
+
remove_instance_variable :@nubmer
|
80
80
|
|
81
|
-
|
82
|
-
assert_match "Did you mean?
|
81
|
+
assert_correction :@number, error.corrections
|
82
|
+
assert_match "Did you mean? @number", error.to_s
|
83
83
|
end
|
84
84
|
end
|
@@ -4,6 +4,8 @@ class VariableNameTest < Minitest::Test
|
|
4
4
|
class User
|
5
5
|
def initialize
|
6
6
|
@email_address = 'email_address@address.net'
|
7
|
+
@first_name = nil
|
8
|
+
@last_name = nil
|
7
9
|
end
|
8
10
|
|
9
11
|
def first_name; end
|
@@ -24,56 +26,59 @@ class VariableNameTest < Minitest::Test
|
|
24
26
|
@user = User.new.extend(UserModule)
|
25
27
|
end
|
26
28
|
|
27
|
-
def
|
28
|
-
error = assert_raises(
|
29
|
+
def test_corrections_include_instance_method
|
30
|
+
error = assert_raises(NameError) do
|
29
31
|
@user.instance_eval { flrst_name }
|
30
32
|
end
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
+
@user.instance_eval do
|
35
|
+
remove_instance_variable :@first_name
|
36
|
+
remove_instance_variable :@last_name
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_correction :first_name, error.corrections
|
40
|
+
assert_match "Did you mean? first_name", error.to_s
|
34
41
|
end
|
35
42
|
|
36
|
-
def
|
37
|
-
error = assert_raises(
|
43
|
+
def test_corrections_include_method_from_module
|
44
|
+
error = assert_raises(NameError) do
|
38
45
|
@user.instance_eval { fr0m_module }
|
39
46
|
end
|
40
47
|
|
41
|
-
|
42
|
-
assert_match "Did you mean?
|
48
|
+
assert_correction :from_module, error.corrections
|
49
|
+
assert_match "Did you mean? from_module", error.to_s
|
43
50
|
end
|
44
51
|
|
45
|
-
def
|
46
|
-
person
|
52
|
+
def test_corrections_include_local_variable_name
|
53
|
+
person = person = nil
|
47
54
|
error = (eprson rescue $!) # Do not use @assert_raises here as it changes a scope.
|
48
55
|
|
49
|
-
|
50
|
-
assert_match "Did you mean?
|
56
|
+
assert_correction :person, error.corrections
|
57
|
+
assert_match "Did you mean? person", error.to_s
|
51
58
|
end
|
52
59
|
|
53
|
-
def
|
54
|
-
error = assert_raises(
|
60
|
+
def test_corrections_include_instance_variable_name
|
61
|
+
error = assert_raises(NameError){ @user.to_s }
|
55
62
|
|
56
|
-
|
57
|
-
assert_match "Did you mean?
|
63
|
+
assert_correction :@email_address, error.corrections
|
64
|
+
assert_match "Did you mean? @email_address", error.to_s
|
58
65
|
end
|
59
66
|
|
60
|
-
def
|
61
|
-
error = assert_raises(
|
67
|
+
def test_corrections_include_private_method
|
68
|
+
error = assert_raises(NameError) do
|
62
69
|
@user.instance_eval { cia_code_name }
|
63
70
|
end
|
64
71
|
|
65
|
-
|
66
|
-
assert_match "Did you mean?
|
72
|
+
assert_correction :cia_codename, error.corrections
|
73
|
+
assert_match "Did you mean? cia_codename", error.to_s
|
67
74
|
end
|
68
75
|
|
69
76
|
@@does_exist = true
|
70
77
|
|
71
|
-
def
|
72
|
-
skip if RUBY_ENGINE == 'rbx'
|
73
|
-
|
78
|
+
def test_corrections_include_class_variable_name
|
74
79
|
error = assert_raises(NameError){ @@doesnt_exist }
|
75
80
|
|
76
|
-
|
77
|
-
assert_match "Did you mean?
|
81
|
+
assert_correction :@@does_exist, error.corrections
|
82
|
+
assert_match "Did you mean? @@does_exist", error.to_s
|
78
83
|
end
|
79
84
|
end
|