Skip to content

Commit bf34bbf

Browse files
Merge pull request #302 from 'BetterErrors/feature/hints'
After some refactoring.
2 parents 95d811f + 621cdd7 commit bf34bbf

File tree

6 files changed

+123
-5
lines changed

6 files changed

+123
-5
lines changed

lib/better_errors/error_page.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ def exception_message
6767
exception.message.strip.gsub(/(\r?\n\s*\r?\n)+/, "\n")
6868
end
6969

70+
def exception_hint
71+
exception.hint
72+
end
73+
7074
def active_support_actions
7175
return [] unless defined?(ActiveSupport::ActionableError)
7276

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module BetterErrors
2+
class ExceptionHint
3+
def initialize(exception)
4+
@exception = exception
5+
end
6+
7+
def hint
8+
case exception
9+
when NoMethodError
10+
/\Aundefined method `(?<method>[^']+)' for (?<val>[^:]+):(?<klass>\w+)/.match(exception.message) do |match|
11+
if match[:val] == "nil"
12+
return "Something is `nil` when it probably shouldn't be."
13+
elsif !match[:klass].start_with? '0x'
14+
return "`#{match[:method]}` is being called on a `#{match[:klass]}` object, "\
15+
"which might not be the type of object you were expecting."
16+
end
17+
end
18+
when NameError
19+
/\Aundefined local variable or method `(?<method>[^']+)' for/.match(exception.message) do |match|
20+
return "`#{match[:method]}` is probably misspelled."
21+
end
22+
end
23+
end
24+
25+
private
26+
27+
attr_reader :exception
28+
end
29+
end

lib/better_errors/raised_exception.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
require 'better_errors/exception_hint'
2+
13
# @private
24
module BetterErrors
35
class RaisedException
4-
attr_reader :exception, :message, :backtrace
6+
attr_reader :exception, :message, :backtrace, :hint
57

68
def initialize(exception)
79
if exception.class.name == "ActionView::Template::Error" && exception.respond_to?(:cause)
@@ -23,6 +25,7 @@ def initialize(exception)
2325
@message = exception.message
2426

2527
setup_backtrace
28+
setup_hint
2629
massage_syntax_error
2730
end
2831

@@ -78,5 +81,9 @@ def massage_syntax_error
7881
end
7982
end
8083
end
84+
85+
def setup_hint
86+
@hint = ExceptionHint.new(exception).hint
87+
end
8188
end
8289
end

lib/better_errors/templates/main.erb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
nav.sidebar,
9191
.frame_info {
9292
position: fixed;
93-
top: 95px;
93+
top: 102px;
9494
bottom: 0;
9595

9696
box-sizing: border-box;
@@ -102,7 +102,7 @@
102102
nav.sidebar {
103103
width: 40%;
104104
left: 20px;
105-
top: 115px;
105+
top: 122px;
106106
bottom: 20px;
107107
}
108108

@@ -131,7 +131,7 @@
131131
header.exception {
132132
padding: 18px 20px;
133133

134-
height: 59px;
134+
height: 66px;
135135
min-height: 59px;
136136

137137
overflow: hidden;
@@ -764,6 +764,9 @@
764764
<% end %>
765765
</div>
766766
<% end %>
767+
<% if exception_hint %>
768+
<h2>Hint: <%= exception_hint %></h2>
769+
<% end %>
767770
</header>
768771
</div>
769772

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe BetterErrors::ExceptionHint do
4+
let(:described_instance) { described_class.new(exception) }
5+
6+
describe '#hint' do
7+
subject(:hint) { described_instance.hint }
8+
9+
context "when the exception is a NameError" do
10+
let(:exception) {
11+
begin
12+
foo
13+
rescue NameError => e
14+
e
15+
end
16+
}
17+
18+
it { is_expected.to eq("`foo` is probably misspelled.") }
19+
end
20+
21+
context "when the exception is a NoMethodError" do
22+
let(:exception) {
23+
begin
24+
val.foo
25+
rescue NoMethodError => e
26+
e
27+
end
28+
}
29+
30+
context "on `nil`" do
31+
let(:val) { nil }
32+
33+
it { is_expected.to eq("Something is `nil` when it probably shouldn't be.") }
34+
end
35+
36+
context 'on an unnamed object type' do
37+
let(:val) { Class.new }
38+
39+
it { is_expected.to be_nil }
40+
end
41+
42+
context "on other values" do
43+
let(:val) { 42 }
44+
45+
it {
46+
is_expected.to match(
47+
/`foo` is being called on a `(Integer|Fixnum)` object, which might not be the type of object you were expecting./
48+
)
49+
}
50+
end
51+
end
52+
end
53+
end

spec/better_errors/raised_exception_spec.rb

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
module BetterErrors
55
describe RaisedException do
66
let(:exception) { RuntimeError.new("whoops") }
7-
subject { RaisedException.new(exception) }
7+
subject(:described_instance) { RaisedException.new(exception) }
8+
9+
before do
10+
allow(BetterErrors::ExceptionHint).to receive(:new).and_return(exception_hint)
11+
end
12+
let(:exception_hint) { instance_double(BetterErrors::ExceptionHint, hint: nil) }
813

914
its(:exception) { is_expected.to eq exception }
1015
its(:message) { is_expected.to eq "whoops" }
@@ -103,5 +108,22 @@ def cause
103108
expect(subject.backtrace.first.line).to eq(11)
104109
end
105110
end
111+
112+
describe '#hint' do
113+
subject(:hint) { described_instance.hint }
114+
115+
it 'uses ExceptionHint to get a hint for the exception' do
116+
hint
117+
expect(BetterErrors::ExceptionHint).to have_received(:new).with(exception)
118+
end
119+
120+
context "when ExceptionHint returns a string" do
121+
let(:exception_hint) { instance_double(BetterErrors::ExceptionHint, hint: "Hint text") }
122+
123+
it 'returns the value from ExceptionHint' do
124+
expect(hint).to eq("Hint text")
125+
end
126+
end
127+
end
106128
end
107129
end

0 commit comments

Comments
 (0)