did_you_mean 0.10.0 → 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|