did_you_mean 0.9.10-java → 0.10.0-java

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