did_you_mean 1.3.1 → 1.4.0

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