did_you_mean 0.9.6-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/.travis.yml +35 -0
- data/Appraisals +23 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +110 -0
- data/Rakefile +61 -0
- data/did_you_mean.gemspec +35 -0
- data/ext/did_you_mean/extconf.rb +3 -0
- data/ext/did_you_mean/method_missing.c +20 -0
- data/ext/did_you_mean/org/yukinishijima/ReceiverCapturer.java +57 -0
- data/gemfiles/activerecord_32.gemfile +21 -0
- data/gemfiles/activerecord_40.gemfile +21 -0
- data/gemfiles/activerecord_41.gemfile +21 -0
- data/gemfiles/activerecord_42.gemfile +21 -0
- data/gemfiles/activerecord_edge.gemfile +25 -0
- data/lib/did_you_mean.rb +16 -0
- data/lib/did_you_mean/core_ext/name_error.rb +28 -0
- data/lib/did_you_mean/finders.rb +31 -0
- data/lib/did_you_mean/finders/name_error_finders.rb +24 -0
- data/lib/did_you_mean/finders/name_error_finders/similar_class_finder.rb +50 -0
- data/lib/did_you_mean/finders/name_error_finders/similar_name_finder.rb +61 -0
- data/lib/did_you_mean/finders/similar_attribute_finder.rb +26 -0
- data/lib/did_you_mean/finders/similar_method_finder.rb +45 -0
- data/lib/did_you_mean/levenshtein.rb +49 -0
- data/lib/did_you_mean/receiver_capturer.jar +0 -0
- data/lib/did_you_mean/test_helper.rb +7 -0
- data/lib/did_you_mean/version.rb +3 -0
- data/lib/did_you_mean/word_collection.rb +27 -0
- data/test/all_test.rb +17 -0
- data/test/name_error_extension_test.rb +65 -0
- data/test/no_method_error_extension_test.rb +14 -0
- data/test/null_finder_test.rb +19 -0
- data/test/similar_attribute_finder_test.rb +17 -0
- data/test/similar_class_finder_test.rb +85 -0
- data/test/similar_method_finder_test.rb +60 -0
- data/test/similar_name_finder_test.rb +62 -0
- data/test/test_helper.rb +32 -0
- metadata +165 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.0.0"
|
6
|
+
|
7
|
+
platforms :ruby do
|
8
|
+
gem "sqlite3"
|
9
|
+
end
|
10
|
+
|
11
|
+
platforms :jruby do
|
12
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
13
|
+
end
|
14
|
+
|
15
|
+
platforms :rbx do
|
16
|
+
gem "rubysl", "~> 2.0"
|
17
|
+
gem "racc"
|
18
|
+
gem "rubinius-developer_tools"
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec :path => "../"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.1.0"
|
6
|
+
|
7
|
+
platforms :ruby do
|
8
|
+
gem "sqlite3"
|
9
|
+
end
|
10
|
+
|
11
|
+
platforms :jruby do
|
12
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
13
|
+
end
|
14
|
+
|
15
|
+
platforms :rbx do
|
16
|
+
gem "rubysl", "~> 2.0"
|
17
|
+
gem "racc"
|
18
|
+
gem "rubinius-developer_tools"
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec :path => "../"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.2.0"
|
6
|
+
|
7
|
+
platforms :ruby do
|
8
|
+
gem "sqlite3"
|
9
|
+
end
|
10
|
+
|
11
|
+
platforms :jruby do
|
12
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
13
|
+
end
|
14
|
+
|
15
|
+
platforms :rbx do
|
16
|
+
gem "rubysl", "~> 2.0"
|
17
|
+
gem "racc"
|
18
|
+
gem "rubinius-developer_tools"
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec :path => "../"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
git "git://github.com/rails/rails.git" do
|
6
|
+
gem "activerecord", :require => "activerecord"
|
7
|
+
end
|
8
|
+
|
9
|
+
gem "arel", :github => "rails/arel"
|
10
|
+
|
11
|
+
platforms :ruby do
|
12
|
+
gem "sqlite3"
|
13
|
+
end
|
14
|
+
|
15
|
+
platforms :jruby do
|
16
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
17
|
+
end
|
18
|
+
|
19
|
+
platforms :rbx do
|
20
|
+
gem "rubysl", "~> 2.0"
|
21
|
+
gem "racc"
|
22
|
+
gem "rubinius-developer_tools"
|
23
|
+
end
|
24
|
+
|
25
|
+
gemspec :path => "../"
|
data/lib/did_you_mean.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "interception"
|
2
|
+
|
3
|
+
require "did_you_mean/version"
|
4
|
+
require "did_you_mean/core_ext/name_error"
|
5
|
+
require "did_you_mean/finders"
|
6
|
+
|
7
|
+
module DidYouMean
|
8
|
+
Interception.listen(->(exception, binding) {
|
9
|
+
# On IRB/pry console, this event is called twice. In the second event,
|
10
|
+
# we get IRB/pry binding. So it shouldn't override @frame_binding if
|
11
|
+
# it's already defined.
|
12
|
+
if exception.is_a?(NameError) && !exception.instance_variable_defined?(:@frame_binding)
|
13
|
+
exception.instance_variable_set(:@frame_binding, binding)
|
14
|
+
end
|
15
|
+
})
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class NameError
|
2
|
+
attr_reader :frame_binding
|
3
|
+
|
4
|
+
IGNORED_CALLERS = [
|
5
|
+
/( |`)missing_name'/,
|
6
|
+
/( |`)safe_constantize'/
|
7
|
+
].freeze
|
8
|
+
private_constant :IGNORED_CALLERS
|
9
|
+
|
10
|
+
def to_s_with_did_you_mean
|
11
|
+
msg = original_message
|
12
|
+
msg << did_you_mean?.to_s if IGNORED_CALLERS.all? {|ignored| caller.first(8).grep(ignored).empty? }
|
13
|
+
msg
|
14
|
+
rescue
|
15
|
+
original_message
|
16
|
+
end
|
17
|
+
|
18
|
+
alias original_message to_s
|
19
|
+
alias to_s to_s_with_did_you_mean
|
20
|
+
|
21
|
+
def did_you_mean?
|
22
|
+
finder.did_you_mean?
|
23
|
+
end
|
24
|
+
|
25
|
+
def finder
|
26
|
+
@finder ||= DidYouMean.finders[self.class.to_s].new(self)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "did_you_mean/word_collection"
|
2
|
+
|
3
|
+
module DidYouMean
|
4
|
+
module BaseFinder
|
5
|
+
def did_you_mean?
|
6
|
+
return if similar_words.empty?
|
7
|
+
|
8
|
+
output = "\n\n"
|
9
|
+
output << " Did you mean? #{format(similar_words.first)}\n"
|
10
|
+
output << similar_words.drop(1).map{|word| "#{' ' * 18}#{format(word)}\n" }.join
|
11
|
+
output << " " # for pry
|
12
|
+
end
|
13
|
+
|
14
|
+
def similar_words
|
15
|
+
@similar_words ||= WordCollection.new(words).similar_to(target_word)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class NullFinder
|
20
|
+
def initialize(*); end
|
21
|
+
def did_you_mean?; end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.finders
|
25
|
+
@@finders ||= Hash.new(NullFinder)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'did_you_mean/finders/name_error_finders'
|
30
|
+
require 'did_you_mean/finders/similar_attribute_finder'
|
31
|
+
require 'did_you_mean/finders/similar_method_finder'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
module NameErrorFinders
|
3
|
+
def self.included(*)
|
4
|
+
raise "Do not include this module since it overrides Class.new method."
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.new(exception)
|
8
|
+
klass = if /uninitialized constant/ =~ exception.original_message
|
9
|
+
SimilarClassFinder
|
10
|
+
elsif /undefined local variable or method/ =~ exception.original_message
|
11
|
+
SimilarNameFinder
|
12
|
+
else
|
13
|
+
NullFinder
|
14
|
+
end
|
15
|
+
|
16
|
+
klass.new(exception)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
finders["NameError"] = NameErrorFinders
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'did_you_mean/finders/name_error_finders/similar_name_finder'
|
24
|
+
require 'did_you_mean/finders/name_error_finders/similar_class_finder'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class SimilarClassFinder
|
3
|
+
include BaseFinder
|
4
|
+
attr_reader :class_name, :original_message
|
5
|
+
|
6
|
+
def initialize(exception)
|
7
|
+
@class_name, @original_message = exception.name, exception.original_message
|
8
|
+
end
|
9
|
+
|
10
|
+
def words
|
11
|
+
scopes.flat_map do |scope|
|
12
|
+
scope.constants.map {|name| ConstantName.new(name, scope) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def name_from_message
|
17
|
+
class_name || /([A-Z]\w*$)/.match(original_message)[0]
|
18
|
+
end
|
19
|
+
alias target_word name_from_message
|
20
|
+
|
21
|
+
def similar_words
|
22
|
+
super.map(&:full_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def scopes
|
26
|
+
@scopes ||= scope_base.size.times.map do |count|
|
27
|
+
eval(scope_base[0..(- count)].join("::"))
|
28
|
+
end.reverse << Object
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def scope_base
|
34
|
+
@scope_base ||= (/(([A-Z]\w*::)*)([A-Z]\w*)$/ =~ original_message ? $1 : "").split("::")
|
35
|
+
end
|
36
|
+
|
37
|
+
class ConstantName < String
|
38
|
+
attr_reader :scope
|
39
|
+
|
40
|
+
def initialize(str, scope)
|
41
|
+
super(str.to_s)
|
42
|
+
@scope = scope.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def full_name
|
46
|
+
scope == "Object" ? to_s : "#{scope}::#{to_s}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class SimilarNameFinder
|
3
|
+
include BaseFinder
|
4
|
+
attr_reader :name, :_methods, :_local_variables, :_instance_variables
|
5
|
+
|
6
|
+
def initialize(exception)
|
7
|
+
@name = exception.name
|
8
|
+
@_methods = exception.frame_binding.eval("methods")
|
9
|
+
@_local_variables = exception.frame_binding.eval("local_variables")
|
10
|
+
@_instance_variables = exception.frame_binding.eval("instance_variables").map do |name|
|
11
|
+
name.to_s.tr("@", "")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def words
|
16
|
+
local_variable_names + method_names + instance_variable_names
|
17
|
+
end
|
18
|
+
|
19
|
+
alias target_word name
|
20
|
+
|
21
|
+
def local_variable_names
|
22
|
+
_local_variables.map {|word| LocalVariableName.new(word.to_s) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def similar_local_variables
|
26
|
+
similar_words.select{|word| word.is_a?(LocalVariableName) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_names
|
30
|
+
_methods.map {|word| MethodName.new(word.to_s) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def similar_methods
|
34
|
+
similar_words.select{|word| word.is_a?(MethodName) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def instance_variable_names
|
38
|
+
_instance_variables.map {|word| InstanceVariableName.new(word.to_s) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def similar_instance_variables
|
42
|
+
similar_words.select {|word| word.is_a?(InstanceVariableName) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def format(word)
|
46
|
+
"#{word.prefix}#{word}"
|
47
|
+
end
|
48
|
+
|
49
|
+
class MethodName < String
|
50
|
+
def prefix; "#"; end
|
51
|
+
end
|
52
|
+
|
53
|
+
class LocalVariableName < String
|
54
|
+
def prefix; ""; end
|
55
|
+
end
|
56
|
+
|
57
|
+
class InstanceVariableName < String
|
58
|
+
def prefix; "@"; end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class SimilarAttributeFinder
|
3
|
+
include BaseFinder
|
4
|
+
attr_reader :columns, :attribute_name
|
5
|
+
|
6
|
+
def initialize(exception)
|
7
|
+
@columns = exception.frame_binding.eval("self.class").columns
|
8
|
+
@attribute_name = (/unknown attribute(: | ')(\w+)/ =~ exception.original_message) && $2
|
9
|
+
end
|
10
|
+
|
11
|
+
def words
|
12
|
+
columns.map(&:name)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias target_word attribute_name
|
16
|
+
|
17
|
+
def format(column_name)
|
18
|
+
"%{column}: %{type}" % {
|
19
|
+
column: column_name,
|
20
|
+
type: columns.detect{|c| c.name == column_name }.type
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
finders["ActiveRecord::UnknownAttributeError"] = SimilarAttributeFinder
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class SimilarMethodFinder
|
3
|
+
include BaseFinder
|
4
|
+
attr_reader :method_name, :receiver
|
5
|
+
|
6
|
+
def initialize(exception)
|
7
|
+
@method_name, @receiver = exception.name, exception.receiver
|
8
|
+
end
|
9
|
+
|
10
|
+
def words
|
11
|
+
method_names = receiver.methods + receiver.singleton_methods
|
12
|
+
method_names.delete(@method_name)
|
13
|
+
method_names.uniq
|
14
|
+
end
|
15
|
+
|
16
|
+
alias target_word method_name
|
17
|
+
|
18
|
+
def format(word)
|
19
|
+
"#{separator}#{word}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def class_method?
|
23
|
+
receiver.is_a? Class
|
24
|
+
end
|
25
|
+
|
26
|
+
def separator
|
27
|
+
class_method? ? "." : "#"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if defined?(RUBY_ENGINE)
|
32
|
+
finders["NoMethodError"] = SimilarMethodFinder
|
33
|
+
|
34
|
+
case RUBY_ENGINE
|
35
|
+
when 'ruby'
|
36
|
+
require 'did_you_mean/method_missing'
|
37
|
+
when 'jruby'
|
38
|
+
require 'did_you_mean/receiver_capturer'
|
39
|
+
org.yukinishijima.ReceiverCapturer.setup(JRuby.runtime)
|
40
|
+
NoMethodError.send(:attr, :receiver)
|
41
|
+
else
|
42
|
+
finders.delete("NoMethodError")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
module Levenshtein # :nodoc:
|
3
|
+
# This code is based directly on the Text gem implementation
|
4
|
+
# Returns a value representing the "cost" of transforming str1 into str2
|
5
|
+
def distance(str1, str2)
|
6
|
+
n = str1.length
|
7
|
+
m = str2.length
|
8
|
+
return m if n.zero?
|
9
|
+
return n if m.zero?
|
10
|
+
|
11
|
+
d = (0..m).to_a
|
12
|
+
x = nil
|
13
|
+
|
14
|
+
str1.each_char.with_index(1) do |char1, i|
|
15
|
+
str2.each_char.with_index do |char2, j|
|
16
|
+
cost = (char1 == char2) ? 0 : 1
|
17
|
+
x = min3(
|
18
|
+
d[j+1] + 1, # insertion
|
19
|
+
i + 1, # deletion
|
20
|
+
d[j] + cost # substitution
|
21
|
+
)
|
22
|
+
d[j] = i
|
23
|
+
i = x
|
24
|
+
end
|
25
|
+
d[m] = x
|
26
|
+
end
|
27
|
+
|
28
|
+
x
|
29
|
+
end
|
30
|
+
module_function :distance
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# detects the minimum value out of three arguments. This method is
|
35
|
+
# faster than `[a, b, c].min` and puts less GC pressure.
|
36
|
+
# See https://github.com/yuki24/did_you_mean/pull/1 for a performance
|
37
|
+
# benchmark.
|
38
|
+
def min3(a, b, c)
|
39
|
+
if a < b && a < c
|
40
|
+
a
|
41
|
+
elsif b < c
|
42
|
+
b
|
43
|
+
else
|
44
|
+
c
|
45
|
+
end
|
46
|
+
end
|
47
|
+
module_function :min3
|
48
|
+
end
|
49
|
+
end
|
Binary file
|