what_weve_got_here_is_an_error_to_communicate 0.0.1 → 0.0.2
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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/Readme.md +9 -7
- data/experiments/formatting/other_resources +7 -0
- data/lib/error_to_communicate/at_exit.rb +15 -9
- data/lib/error_to_communicate/config.rb +55 -22
- data/lib/error_to_communicate/exception_info.rb +86 -31
- data/lib/error_to_communicate/format_terminal.rb +146 -0
- data/lib/error_to_communicate/heuristic/exception.rb +22 -0
- data/lib/error_to_communicate/heuristic/load_error.rb +57 -0
- data/lib/error_to_communicate/heuristic/no_method_error.rb +35 -0
- data/lib/error_to_communicate/heuristic/syntax_error.rb +55 -0
- data/lib/error_to_communicate/heuristic/wrong_number_of_arguments.rb +73 -0
- data/lib/error_to_communicate/heuristic.rb +54 -0
- data/lib/error_to_communicate/project.rb +50 -0
- data/lib/error_to_communicate/rspec_formatter.rb +8 -9
- data/lib/error_to_communicate/theme.rb +137 -0
- data/lib/error_to_communicate/version.rb +2 -2
- data/lib/error_to_communicate.rb +4 -2
- data/spec/acceptance/exception_spec.rb +2 -4
- data/spec/acceptance/load_error_spec.rb +23 -0
- data/spec/acceptance/name_error_spec.rb +46 -0
- data/spec/acceptance/no_methood_error_spec.rb +6 -8
- data/spec/acceptance/runtime_error_spec.rb +27 -0
- data/spec/acceptance/short_and_long_require_spec.rb +29 -0
- data/spec/acceptance/spec_helper.rb +4 -3
- data/spec/acceptance/syntax_error_spec.rb +32 -0
- data/spec/acceptance/{argument_error_spec.rb → wrong_number_of_arguments_spec.rb} +1 -1
- data/spec/config_spec.rb +120 -0
- data/spec/heuristic/exception_spec.rb +17 -0
- data/spec/heuristic/load_error_spec.rb +195 -0
- data/spec/heuristic/no_method_error_spec.rb +25 -0
- data/spec/heuristic/spec_helper.rb +33 -0
- data/spec/heuristic/wrong_number_of_arguments_spec.rb +115 -0
- data/spec/heuristic_spec.rb +76 -0
- data/spec/parsing_exception_info_spec.rb +212 -0
- data/spec/rspec_formatter_spec.rb +3 -1
- data/spec/spec_helper.rb +28 -1
- data/what_weve_got_here_is_an_error_to_communicate.gemspec +2 -2
- metadata +29 -19
- data/lib/error_to_communicate/format/terminal_helpers.rb +0 -97
- data/lib/error_to_communicate/format.rb +0 -132
- data/lib/error_to_communicate/parse/backtrace.rb +0 -34
- data/lib/error_to_communicate/parse/exception.rb +0 -21
- data/lib/error_to_communicate/parse/no_method_error.rb +0 -27
- data/lib/error_to_communicate/parse/registry.rb +0 -30
- data/lib/error_to_communicate/parse/wrong_number_of_arguments.rb +0 -35
- data/spec/parse/backtrace_spec.rb +0 -101
- data/spec/parse/exception_spec.rb +0 -14
- data/spec/parse/no_method_error_spec.rb +0 -23
- data/spec/parse/registered_parsers_spec.rb +0 -68
- data/spec/parse/spec_helper.rb +0 -23
- data/spec/parse/wrong_number_of_arguments_spec.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 502a20709d4fe294ae357bd9652fa9442c14b851
|
4
|
+
data.tar.gz: 8dabffcbda862538f2f3b3791b00b880ed2e9be5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f499bc2afd237666020abca95c8938f94a57955906f96340be86c0a67a54e1a09d8862329309eb2c672195360282aff51ab8a22a9555deac9bf923ec1f1ad226
|
7
|
+
data.tar.gz: 2bbd0c0bf81e0ed3fd07aa3eef2552468381e3fccd88b42ca0adc477b2eec10cb3ff6edb2070612b4d826820f655547e204fbb2bf5c8f94a29e20ae455a4eb15
|
data/.rspec
CHANGED
data/Readme.md
CHANGED
@@ -3,19 +3,16 @@
|
|
3
3
|
What if error messages were compelling to read?
|
4
4
|
-----------------------------------------------
|
5
5
|
|
6
|
-
Blog explaining the
|
7
|
-
(or will be once it's merged).
|
6
|
+
Blog explaining the goal [here](http://blog.turing.io/2015/01/18/what-we-ve-got-here-is-an-error-to-communicate/).
|
8
7
|
|
9
8
|
A screenshot of the code rendering an `ArgumentError`.
|
10
9
|
|
11
10
|

|
12
11
|
|
13
|
-
This is
|
14
|
-
|
12
|
+
This is still early and Rough
|
13
|
+
-----------------------------
|
15
14
|
|
16
|
-
|
17
|
-
If there is resonance in the community, I'll probably try to make it a real gem.
|
18
|
-
I'll have some time to do that during my next braeak in late march 2015.
|
15
|
+
But I've been using it on its own test suite, and have to say it's compelling!
|
19
16
|
|
20
17
|
Inspirations:
|
21
18
|
-------------
|
@@ -25,8 +22,13 @@ Inspirations:
|
|
25
22
|
* Got to thinking about it again with Kerri Miller, conversing at DCamp,
|
26
23
|
and then at Ruby Conf, she created [chatty_exceptions](https://github.com/kerrizor/chatty_exceptions)
|
27
24
|
which is in this same domain.
|
25
|
+
|
26
|
+
Related Projects:
|
27
|
+
-----------------
|
28
|
+
|
28
29
|
* Charlie Sommerville's [better_errors](https://rubygems.org/gems/better_errors)
|
29
30
|
gem gives you a nice interface like this for Rails.
|
31
|
+
* Koichi's [pretty_backtrace](https://github.com/ko1/pretty_backtrace)
|
30
32
|
|
31
33
|
License
|
32
34
|
--------
|
@@ -0,0 +1,7 @@
|
|
1
|
+
This lib is for working with colours
|
2
|
+
https://github.com/plashchynski/rgb
|
3
|
+
|
4
|
+
if it's well written, could maybe use it to allow you to write css,
|
5
|
+
and then translate the colours to console rgb values (eg the way I
|
6
|
+
printed pictures on the console at
|
7
|
+
https://github.com/JoshCheek/print-png/blob/c73fe7e8e29f44fb420240b4c3d3e134625ede3d/screenshot-rainbow.png)
|
@@ -1,11 +1,17 @@
|
|
1
1
|
require 'error_to_communicate'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
2
|
+
|
3
|
+
# Deal with global deps and console knowledge here
|
4
|
+
at_exit do
|
5
|
+
exception = $!
|
6
|
+
config = ErrorToCommunicate::Config.default
|
7
|
+
|
8
|
+
next unless config.accept? exception
|
9
|
+
|
10
|
+
heuristic = config.heuristic_for exception
|
11
|
+
formatted = config.format heuristic, Dir.pwd
|
12
|
+
$stderr.puts formatted
|
13
|
+
|
14
|
+
# There has got to be a better way to clear an exception,
|
15
|
+
# this could break other at_exit hooks -.^
|
16
|
+
exit! 1
|
11
17
|
end
|
@@ -1,32 +1,65 @@
|
|
1
|
+
require 'pathname'
|
1
2
|
require 'error_to_communicate/version'
|
2
|
-
require 'error_to_communicate/
|
3
|
-
require 'error_to_communicate/
|
4
|
-
require 'error_to_communicate/
|
5
|
-
|
3
|
+
require 'error_to_communicate/theme'
|
4
|
+
require 'error_to_communicate/project'
|
5
|
+
require 'error_to_communicate/exception_info'
|
6
|
+
|
7
|
+
module ErrorToCommunicate
|
8
|
+
autoload :FormatTerminal, 'error_to_communicate/format_terminal'
|
6
9
|
|
7
|
-
module WhatWeveGotHereIsAnErrorToCommunicate
|
8
10
|
class Config
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
# Freezing this to encourage duping it rather than modifying the global default.
|
12
|
+
# This implies we should provide a way to add/remove heuristics on the config itself.
|
13
|
+
require 'error_to_communicate/heuristic/wrong_number_of_arguments'
|
14
|
+
require 'error_to_communicate/heuristic/no_method_error'
|
15
|
+
require 'error_to_communicate/heuristic/load_error'
|
16
|
+
require 'error_to_communicate/heuristic/syntax_error'
|
17
|
+
require 'error_to_communicate/heuristic/exception'
|
18
|
+
DEFAULT_HEURISTICS = [
|
19
|
+
Heuristic::WrongNumberOfArguments,
|
20
|
+
Heuristic::NoMethodError,
|
21
|
+
Heuristic::LoadError,
|
22
|
+
Heuristic::SyntaxError,
|
23
|
+
Heuristic::Exception,
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
# Should maybe also be an array, b/c there's no great way to add a proc to the blacklist,
|
27
|
+
# right now, it would have to check it's thing and then call the next one
|
28
|
+
DEFAULT_BLACKLIST = lambda do |einfo|
|
29
|
+
einfo.classname == 'SystemExit'
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.default
|
33
|
+
@default ||= new
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_accessor :heuristics, :blacklist, :theme, :format_with, :catchall_heuristic, :project
|
37
|
+
|
38
|
+
def initialize(options={})
|
39
|
+
self.heuristics = options.fetch(:heuristics) { DEFAULT_HEURISTICS }
|
40
|
+
self.blacklist = options.fetch(:blacklist) { DEFAULT_BLACKLIST }
|
41
|
+
self.theme = options.fetch(:theme) { Theme.new } # this is still really fkn rough
|
42
|
+
self.format_with = options.fetch(:format_with) { FormatTerminal }
|
43
|
+
loaded_features = options.fetch(:loaded_features) { $LOADED_FEATURES }
|
44
|
+
root = options.fetch(:root) { File.expand_path Dir.pwd }
|
45
|
+
self.project = Project.new root: root, loaded_features: loaded_features
|
46
|
+
end
|
47
|
+
|
48
|
+
def accept?(exception)
|
49
|
+
return false unless ExceptionInfo.parseable? exception
|
50
|
+
einfo = ExceptionInfo.parse(exception)
|
51
|
+
!blacklist.call(einfo) && !!heuristics.find { |h| h.for? einfo }
|
22
52
|
end
|
23
53
|
|
24
|
-
def
|
25
|
-
|
54
|
+
def heuristic_for(exception)
|
55
|
+
accept?(exception) || raise(ArgumentError, "Asked for a heuristic on an object we don't accept: #{exception.inspect}")
|
56
|
+
einfo = ExceptionInfo.parse(exception)
|
57
|
+
heuristics.find { |heuristic| heuristic.for? einfo }
|
58
|
+
.new(einfo: einfo, project: project)
|
26
59
|
end
|
27
60
|
|
28
|
-
def
|
29
|
-
|
61
|
+
def format(heuristic, cwd)
|
62
|
+
format_with.call theme: theme, heuristic: heuristic, cwd: Pathname.new(cwd)
|
30
63
|
end
|
31
64
|
end
|
32
65
|
end
|
@@ -1,41 +1,96 @@
|
|
1
|
-
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module ErrorToCommunicate
|
2
4
|
class ExceptionInfo
|
3
|
-
attr_accessor :classname, :explanation, :backtrace
|
4
|
-
attr_accessor :exception # for dev info only, parse out additional info rather than interacting with it direclty
|
5
|
-
def initialize(attributes)
|
6
|
-
self.exception = attributes.fetch :exception, nil
|
7
|
-
self.classname = attributes.fetch :classname
|
8
|
-
self.explanation = attributes.fetch :explanation
|
9
|
-
self.backtrace = attributes.fetch :backtrace
|
10
|
-
end
|
11
5
|
end
|
6
|
+
end
|
12
7
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
8
|
+
class ErrorToCommunicate::ExceptionInfo::Location
|
9
|
+
attr_accessor :path, :linenum, :label, :pred, :succ
|
10
|
+
|
11
|
+
def initialize(attributes)
|
12
|
+
self.path = Pathname.new attributes.fetch(:path)
|
13
|
+
self.linenum = attributes.fetch :linenum
|
14
|
+
self.label = attributes.fetch :label
|
15
|
+
self.pred = attributes.fetch :pred, nil
|
16
|
+
self.succ = attributes.fetch :succ, nil
|
23
17
|
end
|
24
18
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
# What if the line doesn't match for some reason?
|
20
|
+
# Raise an exception?
|
21
|
+
# Use some reasonable default? (is there one?)
|
22
|
+
def self.parse(line)
|
23
|
+
line =~ /^(.*?):(\d+):in `(.*?)'$/ # Are ^ and $ sufficient? Should be \A and (\Z or \z)?
|
24
|
+
ErrorToCommunicate::ExceptionInfo::Location.new(
|
25
|
+
path: $1,
|
26
|
+
linenum: $2.to_i,
|
27
|
+
label: $3,
|
28
|
+
)
|
32
29
|
end
|
33
30
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
# is there an upper bound I need to stay within? Guessing 30 or 31 bits,
|
32
|
+
# but maybe it doesn't really matter?
|
33
|
+
def hash
|
34
|
+
path.hash + linenum.hash + label.hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(location)
|
38
|
+
path == location.path &&
|
39
|
+
linenum == location.linenum &&
|
40
|
+
label == location.label
|
41
|
+
end
|
42
|
+
|
43
|
+
alias eql? ==
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
"#<ExInfo::Loc #{path}:#{linenum}:in `#{label}' pred:#{!!pred} succ:#{!!succ}>"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Wraps an exception in our internal data structures
|
51
|
+
# So we can normalize the different things that come in,
|
52
|
+
# and you can use code that needs this info without
|
53
|
+
# going to the effort of raising exceptions
|
54
|
+
class ErrorToCommunicate::ExceptionInfo
|
55
|
+
attr_accessor :classname
|
56
|
+
attr_accessor :message
|
57
|
+
attr_accessor :backtrace
|
58
|
+
|
59
|
+
def initialize(attributes)
|
60
|
+
self.exception = attributes.fetch :exception, nil
|
61
|
+
self.classname = attributes.fetch :classname
|
62
|
+
self.message = attributes.fetch :message
|
63
|
+
self.backtrace = attributes.fetch :backtrace
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_writer :exception
|
67
|
+
private :exception=
|
68
|
+
def exception
|
69
|
+
@warned_about_exception ||= begin
|
70
|
+
warn "The exception is recorded for debugging purposes only.\n"
|
71
|
+
warn "Don't write code that depends on it, or we can't generically use the ExceptionInfo structure."
|
72
|
+
true
|
39
73
|
end
|
74
|
+
@exception
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.parseable?(exception)
|
78
|
+
exception.respond_to?(:message) && exception.respond_to?(:backtrace)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.parse(exception)
|
82
|
+
return exception if exception.kind_of? self
|
83
|
+
new exception: exception,
|
84
|
+
classname: exception.class.name,
|
85
|
+
message: exception.message,
|
86
|
+
backtrace: parse_backtrace(exception)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.parse_backtrace(exception)
|
90
|
+
# Really, there are better methods, e.g. backtrace_locations,
|
91
|
+
# but they're unevenly implemented across versions and implementations
|
92
|
+
(exception.backtrace||[])
|
93
|
+
.map { |line| Location.parse line }
|
94
|
+
.tap { |locs| locs.each_cons(2) { |crnt, pred| crnt.pred, pred.succ = pred, crnt } }
|
40
95
|
end
|
41
96
|
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'rouge'
|
2
|
+
|
3
|
+
module ErrorToCommunicate
|
4
|
+
class FormatTerminal
|
5
|
+
def self.call(attributes)
|
6
|
+
new(attributes).call
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :cwd, :theme, :heuristic, :format_code, :heuristic_formatter
|
10
|
+
def initialize(attributes)
|
11
|
+
self.cwd = attributes.fetch :cwd
|
12
|
+
self.theme = attributes.fetch :theme
|
13
|
+
self.heuristic = attributes.fetch :heuristic
|
14
|
+
self.format_code = FormatTerminal::Code.new theme: theme, cwd: cwd # defined below
|
15
|
+
end
|
16
|
+
|
17
|
+
# Really, it seems like #format should be #call,
|
18
|
+
# which implies that there are two objects in here.
|
19
|
+
# One that does the semantic formatting, and one that translates that to text.
|
20
|
+
# But I can't quite see it yet, so going to wait until there are more requirements on this.
|
21
|
+
def call
|
22
|
+
format [
|
23
|
+
heuristic.semantic_summary,
|
24
|
+
heuristic.semantic_info,
|
25
|
+
heuristic.semantic_backtrace,
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
# TODO: not good enough,
|
30
|
+
# These need to be able to take things like:
|
31
|
+
# [:message, ["a", [:explanation, "b"], "c"]]
|
32
|
+
# where "a", and "c", get coloured with the semantics of :message
|
33
|
+
def format(semantic_content)
|
34
|
+
return semantic_content unless semantic_content.kind_of? Array
|
35
|
+
|
36
|
+
meaning, content, *rest = semantic_content
|
37
|
+
case meaning
|
38
|
+
when Array then semantic_content.map { |c| format c }.join
|
39
|
+
when :summary then format([:separator]) + format(content)
|
40
|
+
when :heuristic then format([:separator]) + format(content)
|
41
|
+
when :backtrace then format([:separator]) + format(content)
|
42
|
+
when :separator then theme.separator_line
|
43
|
+
when :columns then theme.columns [content, *rest].map { |c| format c }
|
44
|
+
when :classname then theme.classname format content
|
45
|
+
when :message then theme.message format content
|
46
|
+
when :explanation then theme.explanation format content
|
47
|
+
when :context then theme.context format content
|
48
|
+
when :details then theme.details format content
|
49
|
+
when :code then format_code.call content
|
50
|
+
when :null then ''
|
51
|
+
else raise "Wat is #{meaning.inspect}?"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
class Code
|
57
|
+
# TODO: Should this highlight the location?
|
58
|
+
|
59
|
+
attr_accessor :theme, :cwd
|
60
|
+
|
61
|
+
def initialize(attributes)
|
62
|
+
self.theme = attributes.fetch :theme
|
63
|
+
self.cwd = attributes.fetch :cwd
|
64
|
+
end
|
65
|
+
|
66
|
+
def call(attributes)
|
67
|
+
location = attributes.fetch :location
|
68
|
+
path = location.path
|
69
|
+
line_index = location.linenum - 1
|
70
|
+
highlight = attributes.fetch :highlight, location.label
|
71
|
+
end_index = bound_num min: 0, num: line_index+attributes.fetch(:context).end
|
72
|
+
start_index = bound_num min: 0, num: line_index+attributes.fetch(:context).begin
|
73
|
+
message = attributes.fetch :message, ''
|
74
|
+
message_offset = line_index - start_index
|
75
|
+
|
76
|
+
# first line gives the path
|
77
|
+
path_line = [
|
78
|
+
theme.color_path("#{path_to_dir cwd, path}/"),
|
79
|
+
theme.color_filename(path.basename),
|
80
|
+
":",
|
81
|
+
theme.color_linenum(location.linenum),
|
82
|
+
].join("")
|
83
|
+
|
84
|
+
# then display the code
|
85
|
+
if path.exist?
|
86
|
+
code = File.read(path).lines[start_index..end_index].join("")
|
87
|
+
code = remove_indentation code
|
88
|
+
code = theme.syntax_highlight code
|
89
|
+
code = prefix_linenos_to code, start_index.next, mark: location.linenum
|
90
|
+
code = theme.indent code, " "
|
91
|
+
code = add_message_to code, message_offset, theme.screaming_red(message)
|
92
|
+
code = theme.highlight_text code, message_offset, highlight
|
93
|
+
else
|
94
|
+
code = "Can't find code\n"
|
95
|
+
end
|
96
|
+
|
97
|
+
# adjust for emphasization
|
98
|
+
if attributes.fetch(:emphasis) == :path
|
99
|
+
path_line = theme.underline path_line
|
100
|
+
code = theme.indent code, " "
|
101
|
+
code = theme.desaturate code
|
102
|
+
code = theme.highlight_text code, message_offset, highlight # b/c desaturate really strips color
|
103
|
+
end
|
104
|
+
|
105
|
+
# all together
|
106
|
+
path_line << "\n" << code
|
107
|
+
end
|
108
|
+
|
109
|
+
def path_to_dir(from, to)
|
110
|
+
to.expand_path.relative_path_from(from).dirname
|
111
|
+
rescue ArgumentError
|
112
|
+
return to # eg rbx's core code
|
113
|
+
end
|
114
|
+
|
115
|
+
def bound_num(attributes)
|
116
|
+
num = attributes.fetch :num
|
117
|
+
min = attributes.fetch :min
|
118
|
+
num < min ? min : num
|
119
|
+
end
|
120
|
+
|
121
|
+
def remove_indentation(code)
|
122
|
+
indentation = code.scan(/^\s*/).min_by(&:length)
|
123
|
+
code.gsub(/^#{indentation}/, "")
|
124
|
+
end
|
125
|
+
|
126
|
+
def prefix_linenos_to(code, start_linenum, options)
|
127
|
+
line_to_mark = options.fetch :mark, -1
|
128
|
+
lines = code.lines
|
129
|
+
max_linenum = lines.count + start_linenum - 1 # 1 to translate to indexes
|
130
|
+
linenum_width = max_linenum.to_s.length + 1 # 1 for the colon
|
131
|
+
lines.zip(start_linenum..max_linenum)
|
132
|
+
.map { |line, num|
|
133
|
+
formatted_num = "#{num}:".ljust(linenum_width)
|
134
|
+
formatted_num = theme.mark_linenum formatted_num if line_to_mark == num
|
135
|
+
theme.color_linenum(formatted_num) << " " << line
|
136
|
+
}.join("")
|
137
|
+
end
|
138
|
+
|
139
|
+
def add_message_to(code, offset, message)
|
140
|
+
lines = code.lines
|
141
|
+
lines[offset].chomp! << " " << message << "\n"
|
142
|
+
lines.join("")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'error_to_communicate/heuristic'
|
2
|
+
|
3
|
+
module ErrorToCommunicate
|
4
|
+
class Heuristic
|
5
|
+
class Exception < Heuristic
|
6
|
+
def self.for?(einfo)
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def semantic_info
|
11
|
+
[:heuristic,
|
12
|
+
[:code, {
|
13
|
+
location: backtrace[0],
|
14
|
+
highlight: backtrace[0].label,
|
15
|
+
context: -5..5,
|
16
|
+
emphasis: :code,
|
17
|
+
}]
|
18
|
+
]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'error_to_communicate/heuristic'
|
3
|
+
|
4
|
+
module ErrorToCommunicate
|
5
|
+
class Heuristic
|
6
|
+
class LoadError < Heuristic
|
7
|
+
def self.for?(einfo)
|
8
|
+
einfo.classname == 'LoadError' &&
|
9
|
+
einfo.message.include?(' -- ')
|
10
|
+
end
|
11
|
+
|
12
|
+
def path
|
13
|
+
@path ||= Pathname.new einfo.message.split(' -- ', 2).last
|
14
|
+
end
|
15
|
+
|
16
|
+
def semantic_info
|
17
|
+
heuristic = if relevant_locations.any?
|
18
|
+
relevant_locations.map { |location|
|
19
|
+
[:code, {
|
20
|
+
location: location,
|
21
|
+
highlight: location.label,
|
22
|
+
context: -5..5,
|
23
|
+
message: "Couldn't find #{path.to_s.inspect}",
|
24
|
+
emphasis: :code,
|
25
|
+
}]
|
26
|
+
}
|
27
|
+
else
|
28
|
+
# The newline here implies the semantic analysis needs to get better,
|
29
|
+
# it only does this b/c it should be a block-element, but isn't being sectioned like that, correctly
|
30
|
+
[:context, "Couldn\'t find anything interesting ¯\_(ツ)_/¯\n"]
|
31
|
+
end
|
32
|
+
|
33
|
+
[:heuristic, heuristic]
|
34
|
+
end
|
35
|
+
|
36
|
+
def relevant_locations
|
37
|
+
@relevant_locations ||= [first_nongem_line, first_line_within_lib].compact.uniq
|
38
|
+
end
|
39
|
+
|
40
|
+
def first_nongem_line
|
41
|
+
relevant_backtrace.first
|
42
|
+
end
|
43
|
+
|
44
|
+
def first_line_within_lib
|
45
|
+
@first_line_within_lib ||= relevant_backtrace.find { |loc| loc.path.to_s.start_with? project.root }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def relevant_backtrace
|
51
|
+
@relevant_backtrace ||= relevant_backtrace = project.rubygems? ?
|
52
|
+
backtrace :
|
53
|
+
backtrace.drop_while { |loc| loc.path.to_s.start_with? project.rubygems_dir }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'error_to_communicate/heuristic'
|
2
|
+
|
3
|
+
module ErrorToCommunicate
|
4
|
+
class Heuristic
|
5
|
+
class NoMethodError < Heuristic
|
6
|
+
def self.for?(einfo)
|
7
|
+
( einfo.classname == 'NoMethodError' ||
|
8
|
+
einfo.classname == 'NameError'
|
9
|
+
) && parse_undefined_name(einfo)
|
10
|
+
end
|
11
|
+
|
12
|
+
def undefined_method_name
|
13
|
+
self.class.parse_undefined_name einfo
|
14
|
+
end
|
15
|
+
|
16
|
+
def semantic_info
|
17
|
+
[:heuristic,
|
18
|
+
[:code, {
|
19
|
+
location: backtrace[0],
|
20
|
+
highlight: backtrace[0].label,
|
21
|
+
context: -5..5,
|
22
|
+
message: "#{undefined_method_name} is undefined",
|
23
|
+
emphasis: :code,
|
24
|
+
}]
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def self.parse_undefined_name(einfo)
|
31
|
+
einfo.message[/`(.*)'/, 1]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'error_to_communicate/heuristic'
|
2
|
+
|
3
|
+
module ErrorToCommunicate
|
4
|
+
class Heuristic
|
5
|
+
class SyntaxError < Heuristic
|
6
|
+
def self.for?(einfo)
|
7
|
+
einfo.classname == 'SyntaxError' && parse_message(einfo.message)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
attr_accessor :reported_file, :reported_line, :unexpected, :expected, :invalid_loc
|
12
|
+
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
self.reported_file ,
|
16
|
+
self.reported_line ,
|
17
|
+
self.unexpected ,
|
18
|
+
self.expected = self.class.parse_message(einfo.message)
|
19
|
+
self.invalid_loc = ExceptionInfo::Location.new \
|
20
|
+
path: reported_file,
|
21
|
+
linenum: reported_line,
|
22
|
+
label: "unexpected #{unexpected}, expected: #{expected}",
|
23
|
+
pred: backtrace[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
def semantic_info
|
27
|
+
[:heuristic,
|
28
|
+
[:code, {
|
29
|
+
location: invalid_loc,
|
30
|
+
context: -5..5,
|
31
|
+
message: "unexpected #{unexpected}, expected #{expected}",
|
32
|
+
emphasis: :code,
|
33
|
+
}]
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
def semantic_explanation
|
38
|
+
[ :message,
|
39
|
+
[ [:context, 'Unexpected '],
|
40
|
+
[:details, unexpected],
|
41
|
+
[:context, ', expected'],
|
42
|
+
[:details, expected],
|
43
|
+
]
|
44
|
+
]
|
45
|
+
end
|
46
|
+
|
47
|
+
# "/Users/josh/code/what-we-ve-got-here-is-an-error-to-communicate/proving_grounds/simple_syntax_error.rb:2: syntax error, unexpected end-of-input, expecting keyword_end"
|
48
|
+
def self.parse_message(message)
|
49
|
+
return if message !~ /^(.*?):(\d+):.*?unexpected (.*?), expecting (.*)$/
|
50
|
+
file, line, unexpected, expected = $1, $2.to_i, $3, $4
|
51
|
+
[file, line, unexpected, expected]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|