did_you_mean 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +48 -0
  3. data/CHANGELOG.md +75 -75
  4. data/Gemfile +2 -1
  5. data/README.md +2 -32
  6. data/Rakefile +4 -5
  7. data/appveyor.yml +25 -0
  8. data/did_you_mean.gemspec +6 -4
  9. data/documentation/CHANGELOG.md.erb +8 -0
  10. data/documentation/changelog_generator.rb +34 -0
  11. data/documentation/human_typo_api.md +20 -0
  12. data/documentation/tree_spell_algorithm.md +82 -0
  13. data/documentation/tree_spell_checker_api.md +24 -0
  14. data/lib/did_you_mean.rb +17 -16
  15. data/lib/did_you_mean/experimental.rb +2 -2
  16. data/lib/did_you_mean/experimental/initializer_name_correction.rb +1 -1
  17. data/lib/did_you_mean/experimental/ivar_name_correction.rb +3 -1
  18. data/lib/did_you_mean/levenshtein.rb +1 -1
  19. data/lib/did_you_mean/spell_checker.rb +7 -7
  20. data/lib/did_you_mean/spell_checkers/key_error_checker.rb +8 -2
  21. data/lib/did_you_mean/spell_checkers/method_name_checker.rb +14 -6
  22. data/lib/did_you_mean/spell_checkers/name_error_checkers.rb +2 -2
  23. data/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb +5 -5
  24. data/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb +1 -1
  25. data/lib/did_you_mean/tree_spell_checker.rb +137 -0
  26. data/lib/did_you_mean/verbose.rb +2 -2
  27. data/lib/did_you_mean/version.rb +1 -1
  28. data/test/core_ext/test_name_error_extension.rb +48 -0
  29. data/test/edit_distance/{jaro_winkler_test.rb → test_jaro_winkler.rb} +2 -2
  30. data/test/fixtures/mini_dir.yml +15 -0
  31. data/test/fixtures/rspec_dir.yml +112 -0
  32. data/test/helper.rb +29 -0
  33. data/test/spell_checking/{class_name_check_test.rb → test_class_name_check.rb} +12 -10
  34. data/test/spell_checking/{key_name_check_test.rb → test_key_name_check.rb} +18 -8
  35. data/test/spell_checking/{method_name_check_test.rb → test_method_name_check.rb} +17 -15
  36. data/test/spell_checking/{uncorrectable_name_check_test.rb → test_uncorrectable_name_check.rb} +3 -3
  37. data/test/spell_checking/{variable_name_check_test.rb → test_variable_name_check.rb} +18 -16
  38. data/test/{spell_checker_test.rb → test_spell_checker.rb} +2 -2
  39. data/test/test_tree_spell_checker.rb +173 -0
  40. data/test/test_verbose_formatter.rb +21 -0
  41. data/test/tree_spell/change_word.rb +61 -0
  42. data/test/tree_spell/human_typo.rb +89 -0
  43. data/test/tree_spell/test_change_word.rb +38 -0
  44. data/test/tree_spell/test_explore.rb +128 -0
  45. data/test/tree_spell/test_human_typo.rb +24 -0
  46. metadata +47 -58
  47. data/.travis.yml +0 -23
  48. data/test/core_ext/name_error_extension_test.rb +0 -51
  49. data/test/experimental/initializer_name_correction_test.rb +0 -15
  50. data/test/experimental/method_name_checker_test.rb +0 -13
  51. data/test/test_helper.rb +0 -13
  52. data/test/verbose_formatter_test.rb +0 -22
@@ -1,4 +1,4 @@
1
- require "did_you_mean/spell_checker"
1
+ require_relative "../spell_checker"
2
2
 
3
3
  module DidYouMean
4
4
  class MethodNameChecker
@@ -43,14 +43,22 @@ module DidYouMean
43
43
  end
44
44
 
45
45
  def corrections
46
- @corrections ||= SpellChecker.new(dictionary: RB_RESERVED_WORDS + method_names).correct(method_name) - NAMES_TO_EXCLUDE[@receiver.class]
46
+ @corrections ||= SpellChecker.new(dictionary: RB_RESERVED_WORDS + method_names).correct(method_name) - names_to_exclude
47
47
  end
48
48
 
49
49
  def method_names
50
- method_names = receiver.methods + receiver.singleton_methods
51
- method_names += receiver.private_methods if @private_call
52
- method_names.uniq!
53
- method_names
50
+ if Object === receiver
51
+ method_names = receiver.methods + receiver.singleton_methods
52
+ method_names += receiver.private_methods if @private_call
53
+ method_names.uniq!
54
+ method_names
55
+ else
56
+ []
57
+ end
58
+ end
59
+
60
+ def names_to_exclude
61
+ Object === receiver ? NAMES_TO_EXCLUDE[receiver.class] : []
54
62
  end
55
63
  end
56
64
  end
@@ -1,5 +1,5 @@
1
- require 'did_you_mean/spell_checkers/name_error_checkers/class_name_checker'
2
- require 'did_you_mean/spell_checkers/name_error_checkers/variable_name_checker'
1
+ require_relative 'name_error_checkers/class_name_checker'
2
+ require_relative 'name_error_checkers/variable_name_checker'
3
3
 
4
4
  module DidYouMean
5
5
  class << (NameErrorCheckers = Object.new)
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
- require 'delegate'
3
- require "did_you_mean/spell_checker"
2
+
3
+ require_relative "../../spell_checker"
4
4
 
5
5
  module DidYouMean
6
6
  class ClassNameChecker
@@ -31,16 +31,16 @@ module DidYouMean
31
31
  end.uniq
32
32
  end
33
33
 
34
- class ClassName < SimpleDelegator
34
+ class ClassName < String
35
35
  attr :namespace
36
36
 
37
37
  def initialize(name, namespace = '')
38
- super(name)
38
+ super(name.to_s)
39
39
  @namespace = namespace
40
40
  end
41
41
 
42
42
  def full_name
43
- self.class.new("#{namespace}#{__getobj__}")
43
+ self.class.new("#{namespace}#{self}")
44
44
  end
45
45
  end
46
46
 
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require "did_you_mean/spell_checker"
3
+ require_relative "../../spell_checker"
4
4
 
5
5
  module DidYouMean
6
6
  class VariableNameChecker
@@ -0,0 +1,137 @@
1
+ module DidYouMean
2
+ # spell checker for a dictionary that has a tree
3
+ # structure, see doc/tree_spell_checker_api.md
4
+ class TreeSpellChecker
5
+ attr_reader :dictionary, :dimensions, :separator, :augment
6
+
7
+ def initialize(dictionary:, separator: '/', augment: nil)
8
+ @dictionary = dictionary
9
+ @separator = separator
10
+ @augment = augment
11
+ @dimensions = parse_dimensions
12
+ end
13
+
14
+ def correct(input)
15
+ plausibles = plausible_dimensions input
16
+ return no_idea(input) if plausibles.empty?
17
+ suggestions = find_suggestions input, plausibles
18
+ return no_idea(input) if suggestions.empty?
19
+ suggestions
20
+ end
21
+
22
+ private
23
+
24
+ def parse_dimensions
25
+ ParseDimensions.new(dictionary, separator).call
26
+ end
27
+
28
+ def find_suggestions(input, plausibles)
29
+ states = plausibles[0].product(*plausibles[1..-1])
30
+ paths = possible_paths states
31
+ leaf = input.split(separator).last
32
+ ideas = find_ideas(paths, leaf)
33
+ ideas.compact.flatten
34
+ end
35
+
36
+ def no_idea(input)
37
+ return [] unless augment
38
+ ::DidYouMean::SpellChecker.new(dictionary: dictionary).correct(input)
39
+ end
40
+
41
+ def find_ideas(paths, leaf)
42
+ paths.map do |path|
43
+ names = find_leaves(path)
44
+ ideas = CorrectElement.new.call names, leaf
45
+ ideas_to_paths ideas, leaf, names, path
46
+ end
47
+ end
48
+
49
+ def ideas_to_paths(ideas, leaf, names, path)
50
+ return nil if ideas.empty?
51
+ return [path + separator + leaf] if names.include? leaf
52
+ ideas.map { |str| path + separator + str }
53
+ end
54
+
55
+ def find_leaves(path)
56
+ dictionary.map do |str|
57
+ next unless str.include? "#{path}#{separator}"
58
+ str.gsub("#{path}#{separator}", '')
59
+ end.compact
60
+ end
61
+
62
+ def possible_paths(states)
63
+ states.map do |state|
64
+ state.join separator
65
+ end
66
+ end
67
+
68
+ def plausible_dimensions(input)
69
+ elements = input.split(separator)[0..-2]
70
+ elements.each_with_index.map do |element, i|
71
+ next if dimensions[i].nil?
72
+ CorrectElement.new.call dimensions[i], element
73
+ end.compact
74
+ end
75
+ end
76
+
77
+ # parses the elements in each dimension
78
+ class ParseDimensions
79
+ def initialize(dictionary, separator)
80
+ @dictionary = dictionary
81
+ @separator = separator
82
+ end
83
+
84
+ def call
85
+ leafless = remove_leaves
86
+ dimensions = find_elements leafless
87
+ dimensions.map do |elements|
88
+ elements.to_set.to_a
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def remove_leaves
95
+ dictionary.map do |a|
96
+ elements = a.split(separator)
97
+ elements[0..-2]
98
+ end.to_set.to_a
99
+ end
100
+
101
+ def find_elements(leafless)
102
+ max_elements = leafless.map(&:size).max
103
+ dimensions = Array.new(max_elements) { [] }
104
+ (0...max_elements).each do |i|
105
+ leafless.each do |elements|
106
+ dimensions[i] << elements[i] unless elements[i].nil?
107
+ end
108
+ end
109
+ dimensions
110
+ end
111
+
112
+ attr_reader :dictionary, :separator
113
+ end
114
+
115
+ # identifies the elements close to element
116
+ class CorrectElement
117
+ def initialize
118
+ end
119
+
120
+ def call(names, element)
121
+ return names if names.size == 1
122
+ str = normalize element
123
+ return [str] if names.include? str
124
+ checker = ::DidYouMean::SpellChecker.new(dictionary: names)
125
+ checker.correct(str)
126
+ end
127
+
128
+ private
129
+
130
+ def normalize(leaf)
131
+ str = leaf.dup
132
+ str.downcase!
133
+ return str unless str.include? '@'
134
+ str.tr!('@', ' ')
135
+ end
136
+ end
137
+ end
@@ -1,4 +1,4 @@
1
- require 'did_you_mean'
2
- require 'did_you_mean/formatters/verbose_formatter'
1
+ require_relative '../did_you_mean'
2
+ require_relative 'formatters/verbose_formatter'
3
3
 
4
4
  DidYouMean.formatter = DidYouMean::VerboseFormatter.new
@@ -1,3 +1,3 @@
1
1
  module DidYouMean
2
- VERSION = "1.3.1"
2
+ VERSION = "1.4.0"
3
3
  end
@@ -0,0 +1,48 @@
1
+ require_relative '../helper'
2
+
3
+ class NameErrorExtensionTest < Test::Unit::TestCase
4
+ SPELL_CHECKERS = DidYouMean::SPELL_CHECKERS
5
+
6
+ class TestSpellChecker
7
+ def initialize(*); end
8
+ def corrections; ["does_exist"]; end
9
+ end
10
+
11
+ def setup
12
+ @org, SPELL_CHECKERS['NameError'] = SPELL_CHECKERS['NameError'], TestSpellChecker
13
+
14
+ @error = assert_raise(NameError){ doesnt_exist }
15
+ end
16
+
17
+ def teardown
18
+ SPELL_CHECKERS['NameError'] = @org
19
+ end
20
+
21
+ def test_message
22
+ assert_match(/Did you mean\? does_exist/, @error.to_s)
23
+ assert_match(/Did you mean\? does_exist/, @error.message)
24
+ end
25
+
26
+ def test_to_s_does_not_make_disruptive_changes_to_error_message
27
+ error = assert_raise(NameError) do
28
+ raise NameError, "uninitialized constant Object"
29
+ end
30
+
31
+ error.to_s
32
+ assert_equal 1, error.to_s.scan("Did you mean?").count
33
+ end
34
+
35
+ def test_correctable_error_objects_are_dumpable
36
+ error =
37
+ begin
38
+ Dir.chdir(__dir__) { File.open('test_name_error_extension.rb') { |f| f.sizee } }
39
+ rescue NoMethodError => e
40
+ e
41
+ end
42
+
43
+ error.to_s
44
+
45
+ assert_equal "undefined method `sizee' for #<File:test_name_error_extension.rb (closed)>",
46
+ Marshal.load(Marshal.dump(error)).original_message
47
+ end
48
+ end
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require_relative '../helper'
2
2
 
3
3
  # These tests were originally written by Jian Weihang (簡煒航) as part of his work
4
4
  # on the jaro_winkler gem. The original code could be found here:
@@ -6,7 +6,7 @@ require 'test_helper'
6
6
  #
7
7
  # Copyright (c) 2014 Jian Weihang
8
8
 
9
- class JaroWinklerTest < Minitest::Test
9
+ class JaroWinklerTest < Test::Unit::TestCase
10
10
  def test_jaro_winkler_distance
11
11
  assert_distance 0.9667, 'henka', 'henkan'
12
12
  assert_distance 1.0, 'al', 'al'
@@ -0,0 +1,15 @@
1
+ ---
2
+ - test/core_ext/name_error_extension_test.rb
3
+ - test/edit_distance/jaro_winkler_test.rb
4
+ - test/fixtures/book.rb
5
+ - test/spell_checker_test.rb
6
+ - test/spell_checking/class_name_check_test.rb
7
+ - test/spell_checking/key_name_check_test.rb
8
+ - test/spell_checking/method_name_check_test.rb
9
+ - test/spell_checking/uncorrectable_name_check_test.rb
10
+ - test/spell_checking/variable_name_check_test.rb
11
+ - test/test_helper.rb
12
+ - test/tree_spell_checker_test.rb
13
+ - test/tree_spell_explore_test.rb
14
+ - test/tree_spell_human_typo_test.rb
15
+ - test/verbose_formatter_test.rb
@@ -0,0 +1,112 @@
1
+ ---
2
+ - spec/spec_helper.rb
3
+ - spec/integration/suite_hooks_errors_spec.rb
4
+ - spec/integration/filtering_spec.rb
5
+ - spec/integration/spec_file_load_errors_spec.rb
6
+ - spec/integration/failed_line_detection_spec.rb
7
+ - spec/integration/persistence_failures_spec.rb
8
+ - spec/integration/bisect_runners_spec.rb
9
+ - spec/integration/order_spec.rb
10
+ - spec/integration/fail_if_no_examples_spec.rb
11
+ - spec/integration/bisect_spec.rb
12
+ - spec/integration/output_stream_spec.rb
13
+ - spec/support/sandboxing.rb
14
+ - spec/support/spec_files.rb
15
+ - spec/support/fake_libs/json.rb
16
+ - spec/support/fake_libs/open3.rb
17
+ - spec/support/fake_libs/drb/acl.rb
18
+ - spec/support/fake_libs/drb/drb.rb
19
+ - spec/support/fake_libs/mocha/api.rb
20
+ - spec/support/fake_libs/test/unit/assertions.rb
21
+ - spec/support/fake_libs/flexmock/rspec.rb
22
+ - spec/support/fake_libs/rake/tasklib.rb
23
+ - spec/support/fake_libs/coderay.rb
24
+ - spec/support/fake_libs/rr.rb
25
+ - spec/support/fake_libs/rake.rb
26
+ - spec/support/fake_libs/erb.rb
27
+ - spec/support/fake_libs/rspec/mocks.rb
28
+ - spec/support/fake_libs/rspec/expectations.rb
29
+ - spec/support/fake_libs/minitest/assertions.rb
30
+ - spec/support/fake_libs/minitest.rb
31
+ - spec/support/matchers.rb
32
+ - spec/support/runner_support.rb
33
+ - spec/support/isolated_home_directory.rb
34
+ - spec/support/config_options_helper.rb
35
+ - spec/support/mathn_integration_support.rb
36
+ - spec/support/helper_methods.rb
37
+ - spec/support/formatter_support.rb
38
+ - spec/support/fake_bisect_runner.rb
39
+ - spec/support/shared_example_groups.rb
40
+ - spec/support/aruba_support.rb
41
+ - spec/rspec/core/runner_spec.rb
42
+ - spec/rspec/core/did_you_mean_spec.rb
43
+ - spec/rspec/core/drb_spec.rb
44
+ - spec/rspec/core/metadata_spec.rb
45
+ - spec/rspec/core/example_group_spec.rb
46
+ - spec/rspec/core/configuration/only_failures_support_spec.rb
47
+ - spec/rspec/core/rake_task_spec.rb
48
+ - spec/rspec/core/memoized_helpers_spec.rb
49
+ - spec/rspec/core/ordering_spec.rb
50
+ - spec/rspec/core/option_parser_spec.rb
51
+ - spec/rspec/core/example_execution_result_spec.rb
52
+ - spec/rspec/core/suite_hooks_spec.rb
53
+ - spec/rspec/core/set_spec.rb
54
+ - spec/rspec/core/configuration_spec.rb
55
+ - spec/rspec/core/rspec_matchers_spec.rb
56
+ - spec/rspec/core/hooks_filtering_spec.rb
57
+ - spec/rspec/core/bisect/shell_command_spec.rb
58
+ - spec/rspec/core/bisect/server_spec.rb
59
+ - spec/rspec/core/bisect/example_minimizer_spec.rb
60
+ - spec/rspec/core/bisect/shell_runner_spec.rb
61
+ - spec/rspec/core/bisect/utilities_spec.rb
62
+ - spec/rspec/core/bisect/coordinator_spec.rb
63
+ - spec/rspec/core/resources/a_foo.rb
64
+ - spec/rspec/core/resources/formatter_specs.rb
65
+ - spec/rspec/core/resources/inconsistently_ordered_specs.rb
66
+ - spec/rspec/core/resources/a_bar.rb
67
+ - spec/rspec/core/resources/utf8_encoded.rb
68
+ - spec/rspec/core/resources/a_spec.rb
69
+ - spec/rspec/core/resources/acceptance/bar.rb
70
+ - spec/rspec/core/resources/acceptance/foo_spec.rb
71
+ - spec/rspec/core/resources/custom_example_group_runner.rb
72
+ - spec/rspec/core/failed_example_notification_spec.rb
73
+ - spec/rspec/core/hooks_spec.rb
74
+ - spec/rspec/core/formatters/profile_formatter_spec.rb
75
+ - spec/rspec/core/formatters/deprecation_formatter_spec.rb
76
+ - spec/rspec/core/formatters/syntax_highlighter_spec.rb
77
+ - spec/rspec/core/formatters/base_text_formatter_spec.rb
78
+ - spec/rspec/core/formatters/snippet_extractor_spec.rb
79
+ - spec/rspec/core/formatters/progress_formatter_spec.rb
80
+ - spec/rspec/core/formatters/html_snippet_extractor_spec.rb
81
+ - spec/rspec/core/formatters/helpers_spec.rb
82
+ - spec/rspec/core/formatters/html_formatter_spec.rb
83
+ - spec/rspec/core/formatters/json_formatter_spec.rb
84
+ - spec/rspec/core/formatters/documentation_formatter_spec.rb
85
+ - spec/rspec/core/formatters/exception_presenter_spec.rb
86
+ - spec/rspec/core/formatters/console_codes_spec.rb
87
+ - spec/rspec/core/formatters/fallback_message_formatter_spec.rb
88
+ - spec/rspec/core/invocations_spec.rb
89
+ - spec/rspec/core/configuration_options_spec.rb
90
+ - spec/rspec/core/pending_spec.rb
91
+ - spec/rspec/core/profiler_spec.rb
92
+ - spec/rspec/core/project_initializer_spec.rb
93
+ - spec/rspec/core/aggregate_failures_spec.rb
94
+ - spec/rspec/core/dsl_spec.rb
95
+ - spec/rspec/core/ruby_project_spec.rb
96
+ - spec/rspec/core/formatters_spec.rb
97
+ - spec/rspec/core/metadata_filter_spec.rb
98
+ - spec/rspec/core/example_group_constants_spec.rb
99
+ - spec/rspec/core/world_spec.rb
100
+ - spec/rspec/core/shared_context_spec.rb
101
+ - spec/rspec/core/pending_example_spec.rb
102
+ - spec/rspec/core/filter_manager_spec.rb
103
+ - spec/rspec/core/shared_example_group_spec.rb
104
+ - spec/rspec/core/example_status_persister_spec.rb
105
+ - spec/rspec/core/backtrace_formatter_spec.rb
106
+ - spec/rspec/core/output_wrapper_spec.rb
107
+ - spec/rspec/core/example_spec.rb
108
+ - spec/rspec/core/reporter_spec.rb
109
+ - spec/rspec/core/filterable_item_repository_spec.rb
110
+ - spec/rspec/core/notifications_spec.rb
111
+ - spec/rspec/core/warnings_spec.rb
112
+ - spec/rspec/core_spec.rb
@@ -0,0 +1,29 @@
1
+ require 'test/unit'
2
+
3
+ module DidYouMean
4
+ module TestHelper
5
+ class << self
6
+ attr_reader :root
7
+ end
8
+
9
+ if File.file?(File.expand_path('../lib/did_you_mean.rb', __dir__))
10
+ # In this case we're being run from inside the gem, so we just want to
11
+ # require the root of the library
12
+
13
+ @root = File.expand_path('../lib/did_you_mean', __dir__)
14
+ require_relative @root
15
+ else
16
+ # In this case we're being run from inside ruby core, and we want to
17
+ # include the experimental features in the test suite
18
+
19
+ @root = File.expand_path('../../lib/did_you_mean', __dir__)
20
+ require_relative @root
21
+ # We are excluding experimental features for now.
22
+ # require_relative File.join(@root, 'experimental')
23
+ end
24
+
25
+ def assert_correction(expected, array)
26
+ assert_equal Array(expected), array, "Expected #{array.inspect} to only include #{expected.inspect}"
27
+ end
28
+ end
29
+ end