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.
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