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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -18
  3. data/CHANGELOG.md +28 -3
  4. data/Gemfile +0 -7
  5. data/README.md +2 -16
  6. data/Rakefile +1 -13
  7. data/benchmark/memory_usage.rb +4 -4
  8. data/did_you_mean.gemspec +2 -12
  9. data/evaluation/calculator.rb +5 -5
  10. data/lib/did_you_mean.rb +18 -26
  11. data/lib/did_you_mean/core_ext/name_error.rb +15 -25
  12. data/lib/did_you_mean/formatter.rb +5 -11
  13. data/lib/did_you_mean/jaro_winkler.rb +3 -4
  14. data/lib/did_you_mean/levenshtein.rb +1 -1
  15. data/lib/did_you_mean/{finders.rb → spell_checkable.rb} +4 -12
  16. data/lib/did_you_mean/spell_checkers/method_name_checker.rb +54 -0
  17. data/lib/did_you_mean/{finders/name_error_finders.rb → spell_checkers/name_error_checkers.rb} +7 -7
  18. data/lib/did_you_mean/{finders/name_error_finders/class_finder.rb → spell_checkers/name_error_checkers/class_name_checker.rb} +8 -26
  19. data/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb +20 -0
  20. data/lib/did_you_mean/spell_checkers/null_checker.rb +6 -0
  21. data/lib/did_you_mean/version.rb +1 -1
  22. data/test/core_ext/name_error_extension_test.rb +20 -23
  23. data/test/correctable/class_name_test.rb +14 -14
  24. data/test/correctable/method_name_test.rb +21 -21
  25. data/test/correctable/variable_name_test.rb +30 -25
  26. data/test/{word_collection_test.rb → spell_checker_test.rb} +20 -20
  27. data/test/test_helper.rb +5 -8
  28. metadata +15 -50
  29. data/ext/did_you_mean/extconf.rb +0 -2
  30. data/ext/did_you_mean/method_receiver.c +0 -20
  31. data/lib/did_you_mean/core_ext/no_method_error.rb +0 -46
  32. data/lib/did_you_mean/core_ext/rubinius.rb +0 -16
  33. data/lib/did_you_mean/finders/method_finder.rb +0 -62
  34. data/lib/did_you_mean/finders/name_error_finders/name_finder.rb +0 -18
  35. data/lib/did_you_mean/test_helper.rb +0 -7
  36. 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
@@ -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 NameErrorFinders
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
- ClassFinder
13
+ ClassNameChecker
11
14
  when /undefined local variable or method/, /undefined method/, /uninitialized class variable/
12
- NameFinder
15
+ VariableNameChecker
13
16
  else
14
- NullFinder
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 ClassFinder
5
- include BaseFinder
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 searches
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
- if RUBY_ENGINE == 'jruby'
25
- # Always use the original error message to retrieve the user
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 suggestions
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 : "").split("::")
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
@@ -0,0 +1,6 @@
1
+ module DidYouMean
2
+ class NullChecker
3
+ def initialize(*); end
4
+ def corrections; [] end
5
+ end
6
+ end
@@ -1,3 +1,3 @@
1
1
  module DidYouMean
2
- VERSION = "0.10.0"
2
+ VERSION = "1.0.0.beta2"
3
3
  end
@@ -1,31 +1,30 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class NameErrorExtensionTest < Minitest::Test
4
- class TestFinder
4
+ SPELL_CHECKERS = DidYouMean::SPELL_CHECKERS
5
+
6
+ class TestSpellChecker
5
7
  def initialize(*); end
6
- def suggestions; ["Y U SO SLOW?"]; end
8
+ def corrections; ["Y U SO SLOW?"]; end
7
9
  end
8
10
 
9
11
  def setup
10
- @org = DidYouMean.finders[NAME_ERROR.to_s]
11
- DidYouMean.finders[NAME_ERROR.to_s] = TestFinder
12
+ @org, SPELL_CHECKERS['NameError'] = SPELL_CHECKERS['NameError'], TestSpellChecker
12
13
 
13
- @error = assert_raises(NAME_ERROR){ doesnt_exist }
14
+ @error = assert_raises(NameError){ doesnt_exist }
14
15
  end
15
16
 
16
17
  def teardown
17
- DidYouMean.finders[NAME_ERROR.to_s] = @org
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? Y U SO SLOW?", @error.to_s
28
- assert_match "Did you mean? Y U SO SLOW?", @error.message
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, "finder was created when it shouldn't!"
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 = DidYouMean.finders[NAME_ERROR.to_s]
49
- DidYouMean.finders[NAME_ERROR.to_s] = Boomer
49
+ @org, SPELL_CHECKERS['NameError'] = SPELL_CHECKERS['NameError'], Boomer
50
+ DidYouMean::IGNORED_CALLERS << /( |`)do_not_correct_typo'/
50
51
 
51
- @error = assert_raises(NAME_ERROR){ doesnt_exist }
52
+ @error = assert_raises(NameError){ doesnt_exist }
52
53
  end
53
54
 
54
55
  def teardown
55
- DidYouMean.finders[NAME_ERROR.to_s] = @org
56
- end
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 test_ignore_safe_constantize
63
- assert_nothing_raised { safe_constantize }
60
+ def test_ignore
61
+ assert_nothing_raised { do_not_correct_typo }
64
62
  end
65
63
 
66
64
  private
67
65
 
68
- def safe_constantize; @error.message end
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 test_suggestions
31
+ def test_corrections
32
32
  error = assert_raises(NameError) { ::Bo0k }
33
- assert_suggestion "Book", error.suggestions
33
+ assert_correction "Book", error.corrections
34
34
  end
35
35
 
36
- def test_suggestions_include_case_specific_class_name
36
+ def test_corrections_include_case_specific_class_name
37
37
  error = assert_raises(NameError) { ::Acronym }
38
- assert_suggestion "ACRONYM", error.suggestions
38
+ assert_correction "ACRONYM", error.corrections
39
39
  end
40
40
 
41
- def test_suggestions_include_top_level_class_name
41
+ def test_corrections_include_top_level_class_name
42
42
  error = assert_raises(NameError) { Project.bo0k }
43
- assert_suggestion "Book", error.suggestions
43
+ assert_correction "Book", error.corrections
44
44
  end
45
45
 
46
- def test_names_in_suggestions_have_namespaces
46
+ def test_names_in_corrections_have_namespaces
47
47
  error = assert_raises(NameError) { ::Book::TableofContents }
48
- assert_suggestion "Book::TableOfContents", error.suggestions
48
+ assert_correction "Book::TableOfContents", error.corrections
49
49
  end
50
50
 
51
- def test_suggestions_searches_for_names_in_upper_level_scopes
51
+ def test_corrections_candidates_for_names_in_upper_level_scopes
52
52
  error = assert_raises(NameError) { Book::Page.tableof_contents }
53
- assert_suggestion "Book::TableOfContents", error.suggestions
53
+ assert_correction "Book::TableOfContents", error.corrections
54
54
  end
55
55
 
56
- def test_suggestions_should_work_from_within_instance_method
56
+ def test_corrections_should_work_from_within_instance_method
57
57
  error = assert_raises(NameError) { ::Book.new.tableof_contents }
58
- assert_suggestion "Book::TableOfContents", error.suggestions
58
+ assert_correction "Book::TableOfContents", error.corrections
59
59
  end
60
60
 
61
- def test_suggestions_should_work_from_within_instance_method_on_nested_class
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
- assert_suggestion "Book::TableOfContents", error.suggestions
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 test_suggestions_include_instance_method
32
+ def test_corrections_include_instance_method
33
33
  error = assert_raises(NoMethodError){ @user.flrst_name }
34
34
 
35
- assert_suggestion :first_name, error.suggestions
36
- assert_match "Did you mean? first_name", error.to_s
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 test_suggestions_include_private_method
39
+ def test_corrections_include_private_method
40
40
  error = assert_raises(NoMethodError){ @user.friend }
41
41
 
42
- assert_suggestion :friends, error.suggestions
43
- assert_match "Did you mean? friends", error.to_s
42
+ assert_correction :friends, error.corrections
43
+ assert_match "Did you mean? friends", error.to_s
44
44
  end
45
45
 
46
- def test_suggestions_include_method_from_module
46
+ def test_corrections_include_method_from_module
47
47
  error = assert_raises(NoMethodError){ @user.fr0m_module }
48
48
 
49
- assert_suggestion :from_module, error.suggestions
50
- assert_match "Did you mean? from_module", error.to_s
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 test_suggestions_include_class_method
53
+ def test_corrections_include_class_method
54
54
  error = assert_raises(NoMethodError){ User.l0ad }
55
55
 
56
- assert_suggestion :load, error.suggestions
57
- assert_match "Did you mean? load", error.to_s
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.suggestions, :the_protected_method
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.suggestions, :the_private_method
65
+ refute_includes error.corrections, :the_private_method
66
66
  end
67
67
 
68
- def test_suggestions_when_private_method_is_called_with_args
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
- assert_suggestion :raise, error.suggestions
72
- assert_match "Did you mean? raise", error.to_s
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
- assert_suggestion :@number, error.suggestions
82
- assert_match "Did you mean? @number", error.to_s
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 test_suggestions_include_instance_method
28
- error = assert_raises(NAME_ERROR) do
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
- assert_suggestion :first_name, error.suggestions
33
- assert_match "Did you mean? first_name", error.to_s
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 test_suggestions_include_method_from_module
37
- error = assert_raises(NAME_ERROR) do
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
- assert_suggestion :from_module, error.suggestions
42
- assert_match "Did you mean? from_module", error.to_s
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 test_suggestions_include_local_variable_name
46
- person = nil
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
- assert_suggestion :person, error.suggestions
50
- assert_match "Did you mean? person", error.to_s
56
+ assert_correction :person, error.corrections
57
+ assert_match "Did you mean? person", error.to_s
51
58
  end
52
59
 
53
- def test_suggestions_include_instance_variable_name
54
- error = assert_raises(NAME_ERROR){ @user.to_s }
60
+ def test_corrections_include_instance_variable_name
61
+ error = assert_raises(NameError){ @user.to_s }
55
62
 
56
- assert_suggestion :@email_address, error.suggestions
57
- assert_match "Did you mean? @email_address", error.to_s
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 test_suggestions_include_private_method
61
- error = assert_raises(NAME_ERROR) do
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
- assert_suggestion :cia_codename, error.suggestions
66
- assert_match "Did you mean? cia_codename", error.to_s
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 test_suggestions_include_class_variable_name
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
- assert_suggestion :@@does_exist, error.suggestions
77
- assert_match "Did you mean? @@does_exist", error.to_s
81
+ assert_correction :@@does_exist, error.corrections
82
+ assert_match "Did you mean? @@does_exist", error.to_s
78
83
  end
79
84
  end