did_you_mean 0.9.10-java → 0.10.0-java

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +13 -19
  4. data/CHANGELOG.md +229 -0
  5. data/Gemfile +4 -7
  6. data/README.md +5 -16
  7. data/Rakefile +35 -33
  8. data/benchmark/jaro_winkler/memory_usage.rb +12 -0
  9. data/benchmark/jaro_winkler/speed.rb +14 -0
  10. data/benchmark/levenshtein/memory_usage.rb +12 -0
  11. data/benchmark/levenshtein/speed.rb +17 -0
  12. data/benchmark/memory_usage.rb +31 -0
  13. data/did_you_mean.gemspec +9 -2
  14. data/evaluation/calculator.rb +122 -0
  15. data/evaluation/dictionary_generator.rb +36 -0
  16. data/evaluation/incorrect_words.yaml +1159 -0
  17. data/ext/did_you_mean/extconf.rb +1 -1
  18. data/ext/did_you_mean/{method_missing.c → method_receiver.c} +1 -1
  19. data/lib/did_you_mean.rb +22 -3
  20. data/lib/did_you_mean/core_ext/name_error.rb +30 -22
  21. data/lib/did_you_mean/core_ext/no_method_error.rb +13 -2
  22. data/lib/did_you_mean/core_ext/rubinius.rb +16 -0
  23. data/lib/did_you_mean/finders.rb +48 -17
  24. data/lib/did_you_mean/finders/method_finder.rb +62 -0
  25. data/lib/did_you_mean/finders/name_error_finders.rb +8 -11
  26. data/lib/did_you_mean/finders/name_error_finders/class_finder.rb +77 -0
  27. data/lib/did_you_mean/finders/name_error_finders/name_finder.rb +18 -0
  28. data/lib/did_you_mean/formatter.rb +20 -0
  29. data/lib/did_you_mean/jaro_winkler.rb +87 -0
  30. data/lib/did_you_mean/test_helper.rb +1 -1
  31. data/lib/did_you_mean/version.rb +1 -1
  32. data/test/core_ext/name_error_extension_test.rb +74 -0
  33. data/test/{no_method_error_extension_test.rb → core_ext/no_method_error_extension_test.rb} +1 -1
  34. data/test/correctable/class_name_test.rb +65 -0
  35. data/test/correctable/method_name_test.rb +84 -0
  36. data/test/{null_finder_test.rb → correctable/uncorrectable_name_test.rb} +3 -7
  37. data/test/correctable/variable_name_test.rb +79 -0
  38. data/test/edit_distance/jaro_winkler_test.rb +30 -0
  39. data/test/test_helper.rb +2 -20
  40. data/test/word_collection_test.rb +76 -12
  41. metadata +58 -56
  42. data/Appraisals +0 -23
  43. data/gemfiles/activerecord_32.gemfile +0 -21
  44. data/gemfiles/activerecord_40.gemfile +0 -21
  45. data/gemfiles/activerecord_41.gemfile +0 -21
  46. data/gemfiles/activerecord_42.gemfile +0 -21
  47. data/gemfiles/activerecord_edge.gemfile +0 -25
  48. data/lib/did_you_mean/finders/name_error_finders/similar_class_finder.rb +0 -50
  49. data/lib/did_you_mean/finders/name_error_finders/similar_name_finder.rb +0 -61
  50. data/lib/did_you_mean/finders/similar_attribute_finder.rb +0 -26
  51. data/lib/did_you_mean/finders/similar_method_finder.rb +0 -34
  52. data/lib/did_you_mean/word_collection.rb +0 -30
  53. data/test/all_test.rb +0 -18
  54. data/test/name_error_extension_test.rb +0 -73
  55. data/test/similar_attribute_finder_test.rb +0 -17
  56. data/test/similar_class_finder_test.rb +0 -85
  57. data/test/similar_method_finder_test.rb +0 -60
  58. data/test/similar_name_finder_test.rb +0 -62
@@ -0,0 +1,87 @@
1
+ module DidYouMean
2
+ module Jaro
3
+ module_function
4
+
5
+ def distance(str1, str2)
6
+ str1, str2 = str2, str1 if str1.length > str2.length
7
+ length1, length2 = str1.length, str2.length
8
+
9
+ m = 0.0
10
+ t = 0.0
11
+ range = (length2 / 2).floor - 1
12
+ flags1 = 0
13
+ flags2 = 0
14
+
15
+ # Avoid duplicating enumerable objects
16
+ # Also, call #to_a since #codepoints returns an Enumerator on Ruby 1.9.3.
17
+ str1_codepoints = str1.codepoints.to_a
18
+ str2_codepoints = str2.codepoints.to_a
19
+
20
+ i = 0
21
+ while i < length1
22
+ last = i + range
23
+ j = (i >= range) ? i - range : 0
24
+
25
+ while j <= last
26
+ if flags2[j] == 0 && str1_codepoints[i] == str2_codepoints[j]
27
+ flags2 |= (1 << j)
28
+ flags1 |= (1 << i)
29
+ m += 1
30
+ break
31
+ end
32
+
33
+ j += 1
34
+ end
35
+
36
+ i += 1
37
+ end
38
+
39
+ k = i = 0
40
+ while i < length1
41
+ if flags1[i] != 0
42
+ j = index = k
43
+
44
+ k = while j < length2
45
+ index = j
46
+ break(j + 1) if flags2[j] != 0
47
+
48
+ j += 1
49
+ end
50
+
51
+ t += 1 if str1_codepoints[i] != str2_codepoints[index]
52
+ end
53
+
54
+ i += 1
55
+ end
56
+ t = (t / 2).floor
57
+
58
+ m == 0 ? 0 : (m / length1 + m / length2 + (m - t) / m) / 3
59
+ end
60
+ end
61
+
62
+ module JaroWinkler
63
+ WEIGHT = 0.1
64
+ THRESHOLD = 0.7
65
+
66
+ module_function
67
+
68
+ def distance(str1, str2)
69
+ jaro_distance = Jaro.distance(str1, str2)
70
+
71
+ if jaro_distance > THRESHOLD
72
+ codepoints2 = str2.codepoints.to_a
73
+ prefix_bonus = 0
74
+
75
+ i = 0
76
+ str1.each_codepoint do |char1|
77
+ char1 == codepoints2[i] && i < 4 ? prefix_bonus += 1 : break
78
+ i += 1
79
+ end
80
+
81
+ jaro_distance + (prefix_bonus * WEIGHT * (1 - jaro_distance))
82
+ else
83
+ jaro_distance
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,6 +1,6 @@
1
1
  module DidYouMean
2
2
  module TestHelper
3
- def assert_suggestion(array, expected)
3
+ def assert_suggestion(expected, array)
4
4
  assert_equal [expected], array, "Expected #{array.inspect} to only include #{expected.inspect}"
5
5
  end
6
6
  end
@@ -1,3 +1,3 @@
1
1
  module DidYouMean
2
- VERSION = "0.9.10"
2
+ VERSION = "0.10.0"
3
3
  end
@@ -0,0 +1,74 @@
1
+ require 'test_helper'
2
+
3
+ class NameErrorExtensionTest < Minitest::Test
4
+ class TestFinder
5
+ def initialize(*); end
6
+ def suggestions; ["Y U SO SLOW?"]; end
7
+ end
8
+
9
+ def setup
10
+ @org = DidYouMean.finders[NAME_ERROR.to_s]
11
+ DidYouMean.finders[NAME_ERROR.to_s] = TestFinder
12
+
13
+ @error = assert_raises(NAME_ERROR){ doesnt_exist }
14
+ end
15
+
16
+ def teardown
17
+ DidYouMean.finders[NAME_ERROR.to_s] = @org
18
+ end
19
+
20
+ def test_message_provides_original_message
21
+ skip if RUBY_ENGINE == 'rbx'
22
+
23
+ assert_match "undefined local variable or method", @error.to_s
24
+ end
25
+
26
+ 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
29
+ end
30
+
31
+ def test_to_s_does_not_make_disruptive_changes_to_error_message
32
+ error = assert_raises(NameError) do
33
+ raise NameError, "uninitialized constant Object".freeze
34
+ end
35
+
36
+ assert_equal 1, error.to_s.scan("Did you mean?").count
37
+ end
38
+ end
39
+
40
+ class IgnoreCallersTest < Minitest::Test
41
+ class Boomer
42
+ def initialize(*)
43
+ raise Exception, "finder was created when it shouldn't!"
44
+ end
45
+ end
46
+
47
+ def setup
48
+ @org = DidYouMean.finders[NAME_ERROR.to_s]
49
+ DidYouMean.finders[NAME_ERROR.to_s] = Boomer
50
+
51
+ @error = assert_raises(NAME_ERROR){ doesnt_exist }
52
+ end
53
+
54
+ 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 }
60
+ end
61
+
62
+ def test_ignore_safe_constantize
63
+ assert_nothing_raised { safe_constantize }
64
+ end
65
+
66
+ private
67
+
68
+ def safe_constantize; @error.message end
69
+ def missing_name; @error.message end
70
+
71
+ def assert_nothing_raised
72
+ yield
73
+ end
74
+ end
@@ -1,4 +1,4 @@
1
- require_relative 'test_helper'
1
+ require 'test_helper'
2
2
 
3
3
  class NoMethodErrorExtensionTest < Minitest::Test
4
4
  def test_receiver_with_string
@@ -0,0 +1,65 @@
1
+ require 'test_helper'
2
+
3
+ module ACRONYM
4
+ end
5
+
6
+ class Project
7
+ def self.bo0k
8
+ Bo0k
9
+ end
10
+ end
11
+
12
+ class Book
13
+ class TableOfContents; end
14
+
15
+ def tableof_contents
16
+ TableofContents
17
+ end
18
+
19
+ class Page
20
+ def tableof_contents
21
+ TableofContents
22
+ end
23
+
24
+ def self.tableof_contents
25
+ TableofContents
26
+ end
27
+ end
28
+ end
29
+
30
+ class ClassNameTest < Minitest::Test
31
+ def test_suggestions
32
+ error = assert_raises(NameError) { ::Bo0k }
33
+ assert_suggestion "Book", error.suggestions
34
+ end
35
+
36
+ def test_suggestions_include_case_specific_class_name
37
+ error = assert_raises(NameError) { ::Acronym }
38
+ assert_suggestion "ACRONYM", error.suggestions
39
+ end
40
+
41
+ def test_suggestions_include_top_level_class_name
42
+ error = assert_raises(NameError) { Project.bo0k }
43
+ assert_suggestion "Book", error.suggestions
44
+ end
45
+
46
+ def test_names_in_suggestions_have_namespaces
47
+ error = assert_raises(NameError) { ::Book::TableofContents }
48
+ assert_suggestion "Book::TableOfContents", error.suggestions
49
+ end
50
+
51
+ def test_suggestions_searches_for_names_in_upper_level_scopes
52
+ error = assert_raises(NameError) { Book::Page.tableof_contents }
53
+ assert_suggestion "Book::TableOfContents", error.suggestions
54
+ end
55
+
56
+ def test_suggestions_should_work_from_within_instance_method
57
+ error = assert_raises(NameError) { ::Book.new.tableof_contents }
58
+ assert_suggestion "Book::TableOfContents", error.suggestions
59
+ end
60
+
61
+ def test_suggestions_should_work_from_within_instance_method_on_nested_class
62
+ error = assert_raises(NameError) { ::Book::Page.new.tableof_contents }
63
+ assert_suggestion "Book::TableOfContents", error.suggestions
64
+ end
65
+ end
@@ -0,0 +1,84 @@
1
+ require 'test_helper'
2
+
3
+ class MethodNameTest < Minitest::Test
4
+ class User
5
+ def friends; end
6
+ def first_name; end
7
+ def descendants; end
8
+ def call_incorrect_private_method
9
+ raiae NoMethodError
10
+ end
11
+
12
+ protected
13
+ def the_protected_method; end
14
+
15
+ private
16
+ def friend; end
17
+ def the_private_method; end
18
+
19
+ class << self
20
+ def load; end
21
+ end
22
+ end
23
+
24
+ module UserModule
25
+ def from_module; end
26
+ end
27
+
28
+ def setup
29
+ @user = User.new.extend(UserModule)
30
+ end
31
+
32
+ def test_suggestions_include_instance_method
33
+ error = assert_raises(NoMethodError){ @user.flrst_name }
34
+
35
+ assert_suggestion :first_name, error.suggestions
36
+ assert_match "Did you mean? first_name", error.to_s
37
+ end
38
+
39
+ def test_suggestions_include_private_method
40
+ error = assert_raises(NoMethodError){ @user.friend }
41
+
42
+ assert_suggestion :friends, error.suggestions
43
+ assert_match "Did you mean? friends", error.to_s
44
+ end
45
+
46
+ def test_suggestions_include_method_from_module
47
+ error = assert_raises(NoMethodError){ @user.fr0m_module }
48
+
49
+ assert_suggestion :from_module, error.suggestions
50
+ assert_match "Did you mean? from_module", error.to_s
51
+ end
52
+
53
+ def test_suggestions_include_class_method
54
+ error = assert_raises(NoMethodError){ User.l0ad }
55
+
56
+ assert_suggestion :load, error.suggestions
57
+ assert_match "Did you mean? load", error.to_s
58
+ end
59
+
60
+ def test_private_methods_should_not_be_suggested
61
+ error = assert_raises(NoMethodError){ User.new.the_protected_method }
62
+ refute_includes error.suggestions, :the_protected_method
63
+
64
+ error = assert_raises(NoMethodError){ User.new.the_private_method }
65
+ refute_includes error.suggestions, :the_private_method
66
+ end
67
+
68
+ def test_suggestions_when_private_method_is_called_with_args
69
+ error = assert_raises(NoMethodError){ @user.call_incorrect_private_method }
70
+
71
+ assert_suggestion :raise, error.suggestions
72
+ assert_match "Did you mean? raise", error.to_s
73
+ end
74
+
75
+ def test_corrects_incorrect_ivar_name
76
+ skip if RUBY_ENGINE == 'rbx'
77
+
78
+ @number = 1
79
+ error = assert_raises(NoMethodError) { @nubmer.zero? }
80
+
81
+ assert_suggestion :@number, error.suggestions
82
+ assert_match "Did you mean? @number", error.to_s
83
+ end
84
+ end
@@ -1,6 +1,6 @@
1
- require_relative 'test_helper'
1
+ require 'test_helper'
2
2
 
3
- class NullFinderTest < Minitest::Test
3
+ class UncorrectableNameTest < Minitest::Test
4
4
  class FirstNameError < NameError; end
5
5
 
6
6
  def setup
@@ -9,11 +9,7 @@ class NullFinderTest < Minitest::Test
9
9
  end
10
10
  end
11
11
 
12
- def test_did_you_mean?
13
- assert_nil @error.did_you_mean?
14
- end
15
-
16
12
  def test_message
17
- refute_includes "Did you mean?", @error.message
13
+ assert_equal "Other name error", @error.message
18
14
  end
19
15
  end
@@ -0,0 +1,79 @@
1
+ require 'test_helper'
2
+
3
+ class VariableNameTest < Minitest::Test
4
+ class User
5
+ def initialize
6
+ @email_address = 'email_address@address.net'
7
+ end
8
+
9
+ def first_name; end
10
+ def to_s
11
+ "#{@first_name} #{@last_name} <#{email_address}>"
12
+ end
13
+
14
+ private
15
+
16
+ def cia_codename; "Alexa" end
17
+ end
18
+
19
+ module UserModule
20
+ def from_module; end
21
+ end
22
+
23
+ def setup
24
+ @user = User.new.extend(UserModule)
25
+ end
26
+
27
+ def test_suggestions_include_instance_method
28
+ error = assert_raises(NAME_ERROR) do
29
+ @user.instance_eval { flrst_name }
30
+ end
31
+
32
+ assert_suggestion :first_name, error.suggestions
33
+ assert_match "Did you mean? first_name", error.to_s
34
+ end
35
+
36
+ def test_suggestions_include_method_from_module
37
+ error = assert_raises(NAME_ERROR) do
38
+ @user.instance_eval { fr0m_module }
39
+ end
40
+
41
+ assert_suggestion :from_module, error.suggestions
42
+ assert_match "Did you mean? from_module", error.to_s
43
+ end
44
+
45
+ def test_suggestions_include_local_variable_name
46
+ person = nil
47
+ error = (eprson rescue $!) # Do not use @assert_raises here as it changes a scope.
48
+
49
+ assert_suggestion :person, error.suggestions
50
+ assert_match "Did you mean? person", error.to_s
51
+ end
52
+
53
+ def test_suggestions_include_instance_variable_name
54
+ error = assert_raises(NAME_ERROR){ @user.to_s }
55
+
56
+ assert_suggestion :@email_address, error.suggestions
57
+ assert_match "Did you mean? @email_address", error.to_s
58
+ end
59
+
60
+ def test_suggestions_include_private_method
61
+ error = assert_raises(NAME_ERROR) do
62
+ @user.instance_eval { cia_code_name }
63
+ end
64
+
65
+ assert_suggestion :cia_codename, error.suggestions
66
+ assert_match "Did you mean? cia_codename", error.to_s
67
+ end
68
+
69
+ @@does_exist = true
70
+
71
+ def test_suggestions_include_class_variable_name
72
+ skip if RUBY_ENGINE == 'rbx'
73
+
74
+ error = assert_raises(NameError){ @@doesnt_exist }
75
+
76
+ assert_suggestion :@@does_exist, error.suggestions
77
+ assert_match "Did you mean? @@does_exist", error.to_s
78
+ end
79
+ end
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class JaroWinklerTest < Minitest::Test
5
+ def test_jaro_winkler_distance
6
+ assert_distance 0.9667, 'henka', 'henkan'
7
+ assert_distance 1.0, 'al', 'al'
8
+ assert_distance 0.9611, 'martha', 'marhta'
9
+ assert_distance 0.8324, 'jones', 'johnson'
10
+ assert_distance 0.9167, 'abcvwxyz', 'zabcvwxy'
11
+ assert_distance 0.9583, 'abcvwxyz', 'cabvwxyz'
12
+ assert_distance 0.84, 'dwayne', 'duane'
13
+ assert_distance 0.8133, 'dixon', 'dicksonx'
14
+ assert_distance 0.0, 'fvie', 'ten'
15
+ assert_distance 0.9067, 'does_exist', 'doesnt_exist'
16
+ end
17
+
18
+ def test_jarowinkler_distance_with_utf8_strings
19
+ assert_distance 0.9818, '變形金剛4:絕跡重生', '變形金剛4: 絕跡重生'
20
+ assert_distance 0.8222, '連勝文', '連勝丼'
21
+ assert_distance 0.8222, '馬英九', '馬英丸'
22
+ assert_distance 0.6667, '良い', 'いい'
23
+ end
24
+
25
+ private
26
+
27
+ def assert_distance(score, str1, str2)
28
+ assert_equal score, DidYouMean::JaroWinkler.distance(str1, str2).round(4)
29
+ end
30
+ end