Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit cbd6d47

Browse files
obromiosJonRowe
authored andcommitted
Did you mean functionality. (#2601)
* Add did_you_mean functionality to rspec-core. The functionality provides for suggestions when a person enters an rspec spec/some_file_path command with typographical errors. If there are errors, then the code at lib/rspec/core/configuration.rb:2037 searches for suggestions. If useful suggestions are found they are added to the reported exception at lib/rspec/core/configuration.rb:2038. The suggestions are done by the ::DidYouMean::SpellChecker API where available.
1 parent 74ad029 commit cbd6d47

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

lib/rspec/core.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def self.world
139139
module Core
140140
autoload :ExampleStatusPersister, "rspec/core/example_status_persister"
141141
autoload :Profiler, "rspec/core/profiler"
142+
autoload :DidYouMean, "rspec/core/did_you_mean"
142143

143144
# @private
144145
# This avoids issues with reporting time caused by examples that

lib/rspec/core/configuration.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2032,6 +2032,11 @@ def bisect_runner_class
20322032

20332033
def load_file_handling_errors(method, file)
20342034
__send__(method, file)
2035+
rescue LoadError => ex
2036+
relative_file = Metadata.relative_path(file)
2037+
suggestions = DidYouMean.new(relative_file).call
2038+
reporter.notify_non_example_exception(ex, "An error occurred while loading #{relative_file}.#{suggestions}")
2039+
RSpec.world.wants_to_quit = true
20352040
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex
20362041
relative_file = Metadata.relative_path(file)
20372042
reporter.notify_non_example_exception(ex, "An error occurred while loading #{relative_file}.")

lib/rspec/core/did_you_mean.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module RSpec
2+
module Core
3+
# @private
4+
# Wrapper around Ruby's `DidYouMean::SpellChecker` when available to provide file name suggestions.
5+
class DidYouMean
6+
attr_reader :relative_file_name
7+
8+
def initialize(relative_file_name)
9+
@relative_file_name = relative_file_name
10+
end
11+
12+
if defined?(::DidYouMean::SpellChecker)
13+
# provide probable suggestions
14+
def call
15+
checker = ::DidYouMean::SpellChecker.new(:dictionary => Dir["spec/**/*.rb"])
16+
probables = checker.correct(relative_file_name.sub('./', ''))[0..2]
17+
return '' unless probables.any?
18+
19+
formats probables
20+
end
21+
else
22+
# return a hint if API for ::DidYouMean::SpellChecker not supported
23+
def call
24+
"\nHint: Install the `did_you_mean` gem in order to provide suggestions for similarly named files."
25+
end
26+
end
27+
28+
private
29+
30+
def formats(probables)
31+
rspec_format = probables.map { |s, _| "rspec ./#{s}" }
32+
red_font(top_and_tail rspec_format)
33+
end
34+
35+
def top_and_tail(rspec_format)
36+
spaces = ' ' * 20
37+
rspec_format.insert(0, ' - Did you mean?').join("\n#{spaces}") + "\n"
38+
end
39+
40+
def red_font(mytext)
41+
colorizer = ::RSpec::Core::Formatters::ConsoleCodes
42+
colorizer.wrap mytext, :failure
43+
end
44+
end
45+
end
46+
end

spec/rspec/core/did_you_mean_spec.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
module RSpec
2+
module Core
3+
RSpec.describe DidYouMean do
4+
describe '#call' do
5+
context "when `DidYouMean::SpellChecker` is available", :skip => !defined?(::DidYouMean::SpellChecker) do
6+
context 'Success' do
7+
let(:name) { './spec/rspec/core/did_you_mean_spec.rb' }
8+
it 'returns a useful suggestion' do
9+
expect(DidYouMean.new(name[0..-2]).call).to include name
10+
end
11+
context 'numerous possibilities' do
12+
it 'returns a small number of suggestions' do
13+
name = './spec/rspec/core/drb_spec.r'
14+
suggestions = DidYouMean.new(name).call
15+
expect(suggestions.split("\n").size).to eq 4
16+
end
17+
end
18+
end
19+
context 'No suitable suggestions' do
20+
it 'returns empty string' do
21+
name = './' + 'x' * 50
22+
expect(DidYouMean.new(name).call).to eq ''
23+
end
24+
end
25+
end
26+
context "when `DidYouMean::SpellChecker` is not available", :unless => defined?(::DidYouMean::SpellChecker) do
27+
describe 'Success' do
28+
let(:name) { './spec/rspec/core/did_you_mean_spec.rb' }
29+
it 'returns a hint' do
30+
expect(DidYouMean.new(name[0..-2]).call).to include 'Hint:'
31+
end
32+
end
33+
end
34+
end
35+
end
36+
end
37+
end

0 commit comments

Comments
 (0)