dead_end 1.0.2 → 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +9 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile.lock +2 -2
- data/README.md +21 -6
- data/dead_end.gemspec +1 -1
- data/lib/dead_end/auto.rb +60 -7
- data/lib/dead_end/code_line.rb +15 -5
- data/lib/dead_end/lex_all.rb +9 -3
- data/lib/dead_end/version.rb +1 -1
- metadata +3 -4
- data/assets/syntax_search.gif +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1e8a394d94d7d8f70138a9d80d7e660cca8dc06e3d3a5eb9b74334585f284aa
|
4
|
+
data.tar.gz: 75296d7faaf4b117ad45bed40252566ee08752b622b2d90e4afa3be8b4a9b774
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f970086da7e3f662d2dabda9a59d89af73d503dc986a9d469d45b83f25184590892e5bdb0a4c174318b2d1bb201f112c81a0a281d0a530feb49dd7f0eb1fb144
|
7
|
+
data.tar.gz: cc85e81a1d996320128d7656c0cea0727f9672060d7d2747e30e77d2aeb59d3549f4dd230835c5042988c53b1252f975c472774eb2ef7f258caebea5cd5eee40
|
data/.circleci/config.yml
CHANGED
@@ -32,6 +32,14 @@ jobs:
|
|
32
32
|
- ruby/install-deps
|
33
33
|
- <<: *unit
|
34
34
|
|
35
|
+
"ruby-3-0":
|
36
|
+
docker:
|
37
|
+
- image: circleci/ruby:3.0
|
38
|
+
steps:
|
39
|
+
- checkout
|
40
|
+
- ruby/install-deps
|
41
|
+
- <<: *unit
|
42
|
+
|
35
43
|
workflows:
|
36
44
|
version: 2
|
37
45
|
build:
|
@@ -39,3 +47,4 @@ workflows:
|
|
39
47
|
- "ruby-2-5"
|
40
48
|
- "ruby-2-6"
|
41
49
|
- "ruby-2-7"
|
50
|
+
- "ruby-3-0"
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
## HEAD (unreleased)
|
2
2
|
|
3
|
+
## 1.1.4
|
4
|
+
|
5
|
+
- Avoid including demo gif in built gem (https://github.com/zombocom/dead_end/pull/53)
|
6
|
+
|
7
|
+
## 1.1.3
|
8
|
+
|
9
|
+
- Add compatibility with zeitwerk (https://github.com/zombocom/dead_end/pull/52)
|
10
|
+
|
11
|
+
## 1.1.2
|
12
|
+
|
13
|
+
- Namespace Kernel method aliases (https://github.com/zombocom/dead_end/pull/51)
|
14
|
+
|
15
|
+
## 1.1.1
|
16
|
+
|
17
|
+
- Safer NoMethodError annotation (https://github.com/zombocom/dead_end/pull/48)
|
18
|
+
|
19
|
+
## 1.1.0
|
20
|
+
|
21
|
+
- Annotate NoMethodError in non-production environments (https://github.com/zombocom/dead_end/pull/46)
|
22
|
+
- Do not count trailing if/unless as a keyword (https://github.com/zombocom/dead_end/pull/44)
|
23
|
+
|
3
24
|
## 1.0.2
|
4
25
|
|
5
26
|
- Fix bug where empty lines were interpreted to have a zero indentation (https://github.com/zombocom/dead_end/pull/39)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# DeadEnd
|
2
2
|
|
3
|
-
An
|
3
|
+
An error in your code forces you to stop. DeadEnd helps you find those errors to get you back on your way faster.
|
4
4
|
|
5
5
|
DeadEnd: Unmatched `end` detected
|
6
6
|
|
@@ -18,7 +18,7 @@ An AI powered library to find syntax errors in your source code:
|
|
18
18
|
|
19
19
|
## Installation in your codebase
|
20
20
|
|
21
|
-
To automatically
|
21
|
+
To automatically annotate errors when they happen, add this to your Gemfile:
|
22
22
|
|
23
23
|
```ruby
|
24
24
|
gem 'dead_end'
|
@@ -44,7 +44,7 @@ If you're using rspec add this to your `.rspec` file:
|
|
44
44
|
|
45
45
|
## Install the CLI
|
46
46
|
|
47
|
-
To get the CLI and manually search for syntax errors, install the gem:
|
47
|
+
To get the CLI and manually search for syntax errors (but not automatically annotate them), you can manually install the gem:
|
48
48
|
|
49
49
|
$ gem install dead_end
|
50
50
|
|
@@ -54,7 +54,7 @@ This gives you the CLI command `$ dead_end` for more info run `$ dead_end --help
|
|
54
54
|
|
55
55
|
- Missing `end`:
|
56
56
|
|
57
|
-
```
|
57
|
+
```ruby
|
58
58
|
class Dog
|
59
59
|
def bark
|
60
60
|
puts "bark"
|
@@ -68,7 +68,7 @@ end
|
|
68
68
|
|
69
69
|
- Unexpected `end`
|
70
70
|
|
71
|
-
```
|
71
|
+
```ruby
|
72
72
|
class Dog
|
73
73
|
def speak
|
74
74
|
@sounds.each |sound| # Note the missing `do` here
|
@@ -81,6 +81,21 @@ end
|
|
81
81
|
|
82
82
|
As well as unmatched `|` and unmatched `}`. These errors can be time consuming to debug because Ruby often only tells you the last line in the file. The command `ruby -wc path/to/file.rb` can narrow it down a little bit, but this library does a better job.
|
83
83
|
|
84
|
+
## What other errors does it handle?
|
85
|
+
|
86
|
+
In addition to syntax errors, the NoMethodError is annotated to show the line where the error occured, and the surrounding context:
|
87
|
+
|
88
|
+
```
|
89
|
+
scratch.rb:7:in `call': undefined method `upcase' for nil:NilClass (NoMethodError)
|
90
|
+
|
91
|
+
|
92
|
+
1 class Pet
|
93
|
+
6 def call
|
94
|
+
❯ 7 puts "Come here #{@neam.upcase}"
|
95
|
+
8 end
|
96
|
+
9 end
|
97
|
+
```
|
98
|
+
|
84
99
|
## Sounds cool, but why isn't this baked into Ruby directly?
|
85
100
|
|
86
101
|
I would love to get something like this directly in Ruby, but I first need to prove it's useful. The `did_you_mean` functionality started as a gem that was eventually adopted by a bunch of people and then Ruby core liked it enough that they included it in the source. The goal of this gem is to:
|
@@ -90,7 +105,7 @@ I would love to get something like this directly in Ruby, but I first need to pr
|
|
90
105
|
|
91
106
|
## Artificial Inteligence?
|
92
107
|
|
93
|
-
This library uses a goal-seeking algorithm similar to that of a path-finding search. For more information [read the blog post about how it works under the hood](https://schneems.com/2020/12/01/squash-unexpectedend-errors-with-syntaxsearch/).
|
108
|
+
This library uses a goal-seeking algorithm for syntax error detection similar to that of a path-finding search. For more information [read the blog post about how it works under the hood](https://schneems.com/2020/12/01/squash-unexpectedend-errors-with-syntaxsearch/).
|
94
109
|
|
95
110
|
## How does it detect syntax error locations?
|
96
111
|
|
data/dead_end.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
22
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
23
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets)/}) }
|
24
24
|
end
|
25
25
|
spec.bindir = "exe"
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
data/lib/dead_end/auto.rb
CHANGED
@@ -1,29 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
1
3
|
require_relative "../dead_end/internals"
|
2
4
|
|
3
5
|
# Monkey patch kernel to ensure that all `require` calls call the same
|
4
6
|
# method
|
5
7
|
module Kernel
|
6
|
-
|
7
|
-
|
8
|
-
alias_method :
|
8
|
+
module_function
|
9
|
+
|
10
|
+
alias_method :dead_end_original_require, :require
|
11
|
+
alias_method :dead_end_original_require_relative, :require_relative
|
12
|
+
alias_method :dead_end_original_load, :load
|
9
13
|
|
10
14
|
def load(file, wrap = false)
|
11
|
-
|
15
|
+
dead_end_original_load(file)
|
12
16
|
rescue SyntaxError => e
|
13
17
|
DeadEnd.handle_error(e)
|
14
18
|
end
|
15
19
|
|
16
20
|
def require(file)
|
17
|
-
|
21
|
+
dead_end_original_require(file)
|
18
22
|
rescue SyntaxError => e
|
19
23
|
DeadEnd.handle_error(e)
|
20
24
|
end
|
21
25
|
|
22
26
|
def require_relative(file)
|
23
27
|
if Pathname.new(file).absolute?
|
24
|
-
|
28
|
+
dead_end_original_require file
|
25
29
|
else
|
26
|
-
|
30
|
+
dead_end_original_require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
|
27
31
|
end
|
28
32
|
rescue SyntaxError => e
|
29
33
|
DeadEnd.handle_error(e)
|
@@ -49,3 +53,52 @@ class Object
|
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
56
|
+
module DeadEnd
|
57
|
+
IsProduction = -> {
|
58
|
+
ENV["RAILS_ENV"] == "production" || ENV["RACK_ENV"] == "production"
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Unlike a syntax error, a NoMethodError can occur hundreds or thousands of times and
|
63
|
+
# chew up CPU and other resources. Since this is primarilly a "development" optimization
|
64
|
+
# we can attempt to disable this behavior in a production context.
|
65
|
+
if !DeadEnd::IsProduction.call
|
66
|
+
class NoMethodError
|
67
|
+
alias :dead_end_original_to_s :to_s
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
return super if DeadEnd::IsProduction.call
|
71
|
+
|
72
|
+
file, line, _ = backtrace[0].split(":")
|
73
|
+
return super if !File.exist?(file)
|
74
|
+
|
75
|
+
index = line.to_i - 1
|
76
|
+
source = File.read(file)
|
77
|
+
code_lines = DeadEnd::CodeLine.parse(source)
|
78
|
+
|
79
|
+
block = DeadEnd::CodeBlock.new(lines: code_lines[index])
|
80
|
+
lines = DeadEnd::CaptureCodeContext.new(
|
81
|
+
blocks: block,
|
82
|
+
code_lines: code_lines
|
83
|
+
).call
|
84
|
+
|
85
|
+
message = super.dup
|
86
|
+
message << $/
|
87
|
+
message << $/
|
88
|
+
|
89
|
+
message << DeadEnd::DisplayCodeWithLineNumbers.new(
|
90
|
+
lines: lines,
|
91
|
+
highlight_lines: block.lines,
|
92
|
+
terminal: self.class.to_tty?
|
93
|
+
).call
|
94
|
+
|
95
|
+
message << $/
|
96
|
+
message
|
97
|
+
rescue => e
|
98
|
+
puts "DeadEnd Internal error: #{e.dead_end_original_to_s}"
|
99
|
+
puts "DeadEnd Internal backtrace:"
|
100
|
+
puts backtrace.map {|l| " " + l }.join($/)
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/dead_end/code_line.rb
CHANGED
@@ -31,6 +31,12 @@ module DeadEnd
|
|
31
31
|
class CodeLine
|
32
32
|
TRAILING_SLASH = ("\\" + $/).freeze
|
33
33
|
|
34
|
+
def self.parse(source)
|
35
|
+
source.lines.map.with_index do |line, index|
|
36
|
+
CodeLine.new(line: line, index: index)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
34
40
|
attr_reader :line, :index, :indent, :original_line
|
35
41
|
|
36
42
|
def initialize(line: , index:)
|
@@ -51,25 +57,29 @@ module DeadEnd
|
|
51
57
|
end
|
52
58
|
|
53
59
|
private def lex_detect!
|
54
|
-
|
60
|
+
lex_array = LexAll.new(source: line)
|
55
61
|
kw_count = 0
|
56
62
|
end_count = 0
|
57
|
-
|
63
|
+
lex_array.each_with_index do |lex, index|
|
58
64
|
next unless lex.type == :on_kw
|
59
65
|
|
60
66
|
case lex.token
|
61
|
-
when '
|
67
|
+
when 'if', 'unless', 'while', 'until'
|
68
|
+
# Only count if/unless when it's not a "trailing" if/unless
|
69
|
+
# https://github.com/ruby/ruby/blob/06b44f819eb7b5ede1ff69cecb25682b56a1d60c/lib/irb/ruby-lex.rb#L374-L375
|
70
|
+
kw_count += 1 if !lex.expr_label?
|
71
|
+
when 'def', 'case', 'for', 'begin', 'class', 'module', 'do'
|
62
72
|
kw_count += 1
|
63
73
|
when 'end'
|
64
74
|
end_count += 1
|
65
75
|
end
|
66
76
|
end
|
67
77
|
|
68
|
-
@is_comment =
|
78
|
+
@is_comment = lex_array.detect {|lex| lex.type != :on_sp}&.type == :on_comment
|
69
79
|
return if @is_comment
|
70
80
|
@is_kw = (kw_count - end_count) > 0
|
71
81
|
@is_end = (end_count - kw_count) > 0
|
72
|
-
@is_trailing_slash =
|
82
|
+
@is_trailing_slash = lex_array.last.token == TRAILING_SLASH
|
73
83
|
end
|
74
84
|
|
75
85
|
alias :original :original_line
|
data/lib/dead_end/lex_all.rb
CHANGED
@@ -21,7 +21,7 @@ module DeadEnd
|
|
21
21
|
lineno = @lex.last&.first&.first + 1
|
22
22
|
end
|
23
23
|
|
24
|
-
@lex.map! {|(line, _), type, token| LexValue.new(line, _, type, token) }
|
24
|
+
@lex.map! {|(line, _), type, token, state| LexValue.new(line, _, type, token, state) }
|
25
25
|
end
|
26
26
|
|
27
27
|
def each
|
@@ -47,11 +47,17 @@ module DeadEnd
|
|
47
47
|
# lex.type # => :on_indent
|
48
48
|
# lex.token # => "describe"
|
49
49
|
class LexValue
|
50
|
-
attr_reader :line, :type, :token
|
51
|
-
|
50
|
+
attr_reader :line, :type, :token, :state
|
51
|
+
|
52
|
+
def initialize(line, _, type, token, state)
|
52
53
|
@line = line
|
53
54
|
@type = type
|
54
55
|
@token = token
|
56
|
+
@state = state
|
57
|
+
end
|
58
|
+
|
59
|
+
def expr_label?
|
60
|
+
state.allbits?(Ripper::EXPR_LABEL)
|
55
61
|
end
|
56
62
|
end
|
57
63
|
end
|
data/lib/dead_end/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dead_end
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schneems
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: When you get an "unexpected end" in your syntax this gem helps you find
|
14
14
|
it
|
@@ -31,7 +31,6 @@ files:
|
|
31
31
|
- LICENSE.txt
|
32
32
|
- README.md
|
33
33
|
- Rakefile
|
34
|
-
- assets/syntax_search.gif
|
35
34
|
- bin/console
|
36
35
|
- bin/setup
|
37
36
|
- dead_end.gemspec
|
@@ -76,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
75
|
- !ruby/object:Gem::Version
|
77
76
|
version: '0'
|
78
77
|
requirements: []
|
79
|
-
rubygems_version: 3.
|
78
|
+
rubygems_version: 3.0.3
|
80
79
|
signing_key:
|
81
80
|
specification_version: 4
|
82
81
|
summary: Find syntax errors in your source in a snap
|
data/assets/syntax_search.gif
DELETED
Binary file
|