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