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.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.travis.yml +13 -19
- data/CHANGELOG.md +229 -0
- data/Gemfile +4 -7
- data/README.md +5 -16
- data/Rakefile +35 -33
- data/benchmark/jaro_winkler/memory_usage.rb +12 -0
- data/benchmark/jaro_winkler/speed.rb +14 -0
- data/benchmark/levenshtein/memory_usage.rb +12 -0
- data/benchmark/levenshtein/speed.rb +17 -0
- data/benchmark/memory_usage.rb +31 -0
- data/did_you_mean.gemspec +9 -2
- data/evaluation/calculator.rb +122 -0
- data/evaluation/dictionary_generator.rb +36 -0
- data/evaluation/incorrect_words.yaml +1159 -0
- data/ext/did_you_mean/extconf.rb +1 -1
- data/ext/did_you_mean/{method_missing.c → method_receiver.c} +1 -1
- data/lib/did_you_mean.rb +22 -3
- data/lib/did_you_mean/core_ext/name_error.rb +30 -22
- data/lib/did_you_mean/core_ext/no_method_error.rb +13 -2
- data/lib/did_you_mean/core_ext/rubinius.rb +16 -0
- data/lib/did_you_mean/finders.rb +48 -17
- data/lib/did_you_mean/finders/method_finder.rb +62 -0
- data/lib/did_you_mean/finders/name_error_finders.rb +8 -11
- data/lib/did_you_mean/finders/name_error_finders/class_finder.rb +77 -0
- data/lib/did_you_mean/finders/name_error_finders/name_finder.rb +18 -0
- data/lib/did_you_mean/formatter.rb +20 -0
- data/lib/did_you_mean/jaro_winkler.rb +87 -0
- data/lib/did_you_mean/test_helper.rb +1 -1
- data/lib/did_you_mean/version.rb +1 -1
- data/test/core_ext/name_error_extension_test.rb +74 -0
- data/test/{no_method_error_extension_test.rb → core_ext/no_method_error_extension_test.rb} +1 -1
- data/test/correctable/class_name_test.rb +65 -0
- data/test/correctable/method_name_test.rb +84 -0
- data/test/{null_finder_test.rb → correctable/uncorrectable_name_test.rb} +3 -7
- data/test/correctable/variable_name_test.rb +79 -0
- data/test/edit_distance/jaro_winkler_test.rb +30 -0
- data/test/test_helper.rb +2 -20
- data/test/word_collection_test.rb +76 -12
- metadata +58 -56
- data/Appraisals +0 -23
- data/gemfiles/activerecord_32.gemfile +0 -21
- data/gemfiles/activerecord_40.gemfile +0 -21
- data/gemfiles/activerecord_41.gemfile +0 -21
- data/gemfiles/activerecord_42.gemfile +0 -21
- data/gemfiles/activerecord_edge.gemfile +0 -25
- data/lib/did_you_mean/finders/name_error_finders/similar_class_finder.rb +0 -50
- data/lib/did_you_mean/finders/name_error_finders/similar_name_finder.rb +0 -61
- data/lib/did_you_mean/finders/similar_attribute_finder.rb +0 -26
- data/lib/did_you_mean/finders/similar_method_finder.rb +0 -34
- data/lib/did_you_mean/word_collection.rb +0 -30
- data/test/all_test.rb +0 -18
- data/test/name_error_extension_test.rb +0 -73
- data/test/similar_attribute_finder_test.rb +0 -17
- data/test/similar_class_finder_test.rb +0 -85
- data/test/similar_method_finder_test.rb +0 -60
- 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
|
data/lib/did_you_mean/version.rb
CHANGED
@@ -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
|
@@ -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
|
-
|
1
|
+
require 'test_helper'
|
2
2
|
|
3
|
-
class
|
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
|
-
|
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
|