dead_end 1.1.4 → 1.2.0
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 +14 -9
- data/.standard.yml +1 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +29 -2
- data/README.md +1 -19
- data/Rakefile +1 -1
- data/dead_end.gemspec +12 -12
- data/exe/dead_end +3 -3
- data/lib/dead_end/around_block_scan.rb +13 -12
- data/lib/dead_end/auto.rb +3 -52
- data/lib/dead_end/block_expand.rb +6 -5
- data/lib/dead_end/capture_code_context.rb +49 -39
- data/lib/dead_end/code_block.rb +3 -3
- data/lib/dead_end/code_frontier.rb +4 -4
- data/lib/dead_end/code_line.rb +11 -11
- data/lib/dead_end/code_search.rb +22 -19
- data/lib/dead_end/display_code_with_line_numbers.rb +7 -7
- data/lib/dead_end/display_invalid_blocks.rb +7 -8
- data/lib/dead_end/fyi.rb +1 -2
- data/lib/dead_end/heredoc_block_parse.rb +6 -2
- data/lib/dead_end/internals.rb +16 -16
- data/lib/dead_end/lex_all.rb +7 -7
- data/lib/dead_end/parse_blocks_from_indent_line.rb +2 -3
- data/lib/dead_end/trailing_slash_join.rb +2 -2
- data/lib/dead_end/version.rb +1 -1
- data/lib/dead_end/who_dis_syntax_error.rb +19 -11
- metadata +4 -4
- data/.travis.yml +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7db29aed59a901a58a0b1ed50873b6f2c17692f7724ffad2736b99694b78ba0
|
4
|
+
data.tar.gz: e1cf2a11fa38af85df30d89a559b0c6e5a74beec2c0add8b6f67dd142d793de1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 444cfdfd7df93038d1714729a21a3b7a20f9000e9cf0541521d51a125d00be4968e83bbc8a51bf4c4689e2b99706cf5eb0032a318647ecb3fd4452a343798e7a
|
7
|
+
data.tar.gz: c532b87160ae6231776b72ff68f3c8f940064051e14c27da28fa22a15c1be0060d9b2afd600bb66dd4571cd39b9372b1956782565040a5e159776f9dac9f9e62
|
data/.circleci/config.yml
CHANGED
@@ -7,15 +7,12 @@ references:
|
|
7
7
|
name: Run test suite
|
8
8
|
command: bundle exec rspec spec/
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
steps:
|
15
|
-
- checkout
|
16
|
-
- ruby/install-deps
|
17
|
-
- <<: *unit
|
10
|
+
lint: &lint
|
11
|
+
run:
|
12
|
+
name: Run linter, fix with `standardrb --fix` locally
|
13
|
+
command: bundle exec standardrb
|
18
14
|
|
15
|
+
jobs:
|
19
16
|
"ruby-2-6":
|
20
17
|
docker:
|
21
18
|
- image: circleci/ruby:2.6
|
@@ -40,11 +37,19 @@ jobs:
|
|
40
37
|
- ruby/install-deps
|
41
38
|
- <<: *unit
|
42
39
|
|
40
|
+
"lint":
|
41
|
+
docker:
|
42
|
+
- image: circleci/ruby:3.0
|
43
|
+
steps:
|
44
|
+
- checkout
|
45
|
+
- ruby/install-deps
|
46
|
+
- <<: *lint
|
47
|
+
|
43
48
|
workflows:
|
44
49
|
version: 2
|
45
50
|
build:
|
46
51
|
jobs:
|
47
|
-
- "ruby-2-5"
|
48
52
|
- "ruby-2-6"
|
49
53
|
- "ruby-2-7"
|
50
54
|
- "ruby-3-0"
|
55
|
+
- "lint"
|
data/.standard.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby_version: 2.6.6
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
## HEAD (unreleased)
|
2
2
|
|
3
|
+
## 1.2.0
|
4
|
+
|
5
|
+
- Output improvements via less greedy unmatched kw capture https://github.com/zombocom/dead_end/pull/73
|
6
|
+
- Remove NoMethodError patching instead use https://github.com/ruby/error_highlight/ (https://github.com/zombocom/dead_end/pull/71)
|
7
|
+
|
8
|
+
## 1.1.7
|
9
|
+
|
10
|
+
- Fix sinatra support for `require_relative` (https://github.com/zombocom/dead_end/pull/63)
|
11
|
+
|
12
|
+
## 1.1.6
|
13
|
+
|
14
|
+
- Consider if syntax error caused an unexpected variable instead of end (https://github.com/zombocom/dead_end/pull/58)
|
15
|
+
|
16
|
+
## 1.1.5
|
17
|
+
|
18
|
+
- Parse error once and not twice if there's more than one available (https://github.com/zombocom/dead_end/pull/57)
|
19
|
+
|
3
20
|
## 1.1.4
|
4
21
|
|
5
22
|
- Avoid including demo gif in built gem (https://github.com/zombocom/dead_end/pull/53)
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dead_end (1.
|
4
|
+
dead_end (1.2.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
ast (2.4.2)
|
9
10
|
diff-lcs (1.4.4)
|
11
|
+
parallel (1.21.0)
|
12
|
+
parser (3.0.2.0)
|
13
|
+
ast (~> 2.4.1)
|
14
|
+
rainbow (3.0.0)
|
10
15
|
rake (12.3.3)
|
16
|
+
regexp_parser (2.1.1)
|
17
|
+
rexml (3.2.5)
|
11
18
|
rspec (3.10.0)
|
12
19
|
rspec-core (~> 3.10.0)
|
13
20
|
rspec-expectations (~> 3.10.0)
|
@@ -21,7 +28,26 @@ GEM
|
|
21
28
|
diff-lcs (>= 1.2.0, < 2.0)
|
22
29
|
rspec-support (~> 3.10.0)
|
23
30
|
rspec-support (3.10.0)
|
31
|
+
rubocop (1.20.0)
|
32
|
+
parallel (~> 1.10)
|
33
|
+
parser (>= 3.0.0.0)
|
34
|
+
rainbow (>= 2.2.2, < 4.0)
|
35
|
+
regexp_parser (>= 1.8, < 3.0)
|
36
|
+
rexml
|
37
|
+
rubocop-ast (>= 1.9.1, < 2.0)
|
38
|
+
ruby-progressbar (~> 1.7)
|
39
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
40
|
+
rubocop-ast (1.12.0)
|
41
|
+
parser (>= 3.0.1.1)
|
42
|
+
rubocop-performance (1.11.5)
|
43
|
+
rubocop (>= 1.7.0, < 2.0)
|
44
|
+
rubocop-ast (>= 0.4.0)
|
45
|
+
ruby-progressbar (1.11.0)
|
24
46
|
stackprof (0.2.16)
|
47
|
+
standard (1.3.0)
|
48
|
+
rubocop (= 1.20.0)
|
49
|
+
rubocop-performance (= 1.11.5)
|
50
|
+
unicode-display_width (2.1.0)
|
25
51
|
|
26
52
|
PLATFORMS
|
27
53
|
ruby
|
@@ -31,6 +57,7 @@ DEPENDENCIES
|
|
31
57
|
rake (~> 12.0)
|
32
58
|
rspec (~> 3.0)
|
33
59
|
stackprof
|
60
|
+
standard
|
34
61
|
|
35
62
|
BUNDLED WITH
|
36
|
-
2.2.
|
63
|
+
2.2.27
|
data/README.md
CHANGED
@@ -81,27 +81,9 @@ 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
|
-
|
99
84
|
## Sounds cool, but why isn't this baked into Ruby directly?
|
100
85
|
|
101
|
-
|
102
|
-
|
103
|
-
1. Get real world useage and feedback. If we gave you an awful suggestion, let us know! We try to handle lots of cases well, but maybe we could be better.
|
104
|
-
2. Prove out demand. If you like this idea, then vote for it by putting it in your Gemfile.
|
86
|
+
We are now talking about it https://bugs.ruby-lang.org/issues/18159#change-93682.
|
105
87
|
|
106
88
|
## Artificial Inteligence?
|
107
89
|
|
data/Rakefile
CHANGED
data/dead_end.gemspec
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "lib/dead_end/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
6
|
+
spec.name = "dead_end"
|
7
|
+
spec.version = DeadEnd::VERSION
|
8
|
+
spec.authors = ["schneems"]
|
9
|
+
spec.email = ["richard.schneeman+foo@gmail.com"]
|
10
10
|
|
11
|
-
spec.summary
|
12
|
-
spec.description
|
13
|
-
spec.homepage
|
14
|
-
spec.license
|
11
|
+
spec.summary = "Find syntax errors in your source in a snap"
|
12
|
+
spec.description = 'When you get an "unexpected end" in your syntax this gem helps you find it'
|
13
|
+
spec.homepage = "https://github.com/zombocom/dead_end.git"
|
14
|
+
spec.license = "MIT"
|
15
15
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
16
16
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
@@ -19,10 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
|
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
|
-
spec.files
|
22
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
23
23
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets)/}) }
|
24
24
|
end
|
25
|
-
spec.bindir
|
26
|
-
spec.executables
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.require_paths = ["lib"]
|
28
28
|
end
|
data/exe/dead_end
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require "pathname"
|
4
4
|
require "optparse"
|
5
|
-
require_relative "../lib/dead_end
|
5
|
+
require_relative "../lib/dead_end"
|
6
6
|
|
7
7
|
options = {}
|
8
8
|
options[:terminal] = true
|
@@ -60,7 +60,7 @@ end
|
|
60
60
|
file = Pathname(file)
|
61
61
|
options[:record_dir] = "tmp" if ENV["DEBUG"]
|
62
62
|
|
63
|
-
|
63
|
+
warn "Record dir: #{options[:record_dir]}" if options[:record_dir]
|
64
64
|
|
65
65
|
DeadEnd.call(
|
66
66
|
source: file.read,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
3
|
module DeadEnd
|
4
4
|
# This class is useful for exploring contents before and after
|
5
5
|
# a block
|
@@ -28,7 +28,7 @@ module DeadEnd
|
|
28
28
|
#
|
29
29
|
# To grab the next surrounding indentation use AroundBlockScan#scan_adjacent_indent
|
30
30
|
class AroundBlockScan
|
31
|
-
def initialize(code_lines
|
31
|
+
def initialize(code_lines:, block:)
|
32
32
|
@code_lines = code_lines
|
33
33
|
@orig_before_index = block.lines.first.index
|
34
34
|
@orig_after_index = block.lines.last.index
|
@@ -56,7 +56,7 @@ module DeadEnd
|
|
56
56
|
end_count = 0
|
57
57
|
@before_index = before_lines.reverse_each.take_while do |line|
|
58
58
|
next false if stop_next
|
59
|
-
next true if @skip_array.detect {|meth| line.send(meth) }
|
59
|
+
next true if @skip_array.detect { |meth| line.send(meth) }
|
60
60
|
|
61
61
|
kw_count += 1 if line.is_kw?
|
62
62
|
end_count += 1 if line.is_end?
|
@@ -65,14 +65,14 @@ module DeadEnd
|
|
65
65
|
end
|
66
66
|
|
67
67
|
block.call(line)
|
68
|
-
end.
|
68
|
+
end.last&.index
|
69
69
|
|
70
70
|
stop_next = false
|
71
71
|
kw_count = 0
|
72
72
|
end_count = 0
|
73
73
|
@after_index = after_lines.take_while do |line|
|
74
74
|
next false if stop_next
|
75
|
-
next true if @skip_array.detect {|meth| line.send(meth) }
|
75
|
+
next true if @skip_array.detect { |meth| line.send(meth) }
|
76
76
|
|
77
77
|
kw_count += 1 if line.is_kw?
|
78
78
|
end_count += 1 if line.is_end?
|
@@ -89,7 +89,7 @@ module DeadEnd
|
|
89
89
|
lines = []
|
90
90
|
kw_count = 0
|
91
91
|
end_count = 0
|
92
|
-
before_lines.
|
92
|
+
before_lines.reverse_each do |line|
|
93
93
|
next if line.empty?
|
94
94
|
break if line.indent < @orig_indent
|
95
95
|
next if line.indent != @orig_indent
|
@@ -124,14 +124,14 @@ module DeadEnd
|
|
124
124
|
|
125
125
|
lines << line
|
126
126
|
end
|
127
|
-
lines.select! {|line| !line.is_comment? }
|
127
|
+
lines.select! { |line| !line.is_comment? }
|
128
128
|
|
129
129
|
lines
|
130
130
|
end
|
131
131
|
|
132
132
|
def on_falling_indent
|
133
133
|
last_indent = @orig_indent
|
134
|
-
before_lines.
|
134
|
+
before_lines.reverse_each do |line|
|
135
135
|
next if line.empty?
|
136
136
|
if line.indent < last_indent
|
137
137
|
yield line
|
@@ -150,7 +150,7 @@ module DeadEnd
|
|
150
150
|
end
|
151
151
|
|
152
152
|
def scan_neighbors
|
153
|
-
|
153
|
+
scan_while { |line| line.not_empty? && line.indent >= @orig_indent }
|
154
154
|
end
|
155
155
|
|
156
156
|
def next_up
|
@@ -167,13 +167,14 @@ module DeadEnd
|
|
167
167
|
before_after_indent << (next_down&.indent || 0)
|
168
168
|
|
169
169
|
indent = before_after_indent.min
|
170
|
-
|
170
|
+
scan_while { |line| line.not_empty? && line.indent >= indent }
|
171
171
|
|
172
172
|
self
|
173
173
|
end
|
174
174
|
|
175
175
|
def start_at_next_line
|
176
|
-
before_index
|
176
|
+
before_index
|
177
|
+
after_index
|
177
178
|
@before_index -= 1
|
178
179
|
@after_index += 1
|
179
180
|
self
|
@@ -196,7 +197,7 @@ module DeadEnd
|
|
196
197
|
end
|
197
198
|
|
198
199
|
private def after_lines
|
199
|
-
@code_lines[after_index.next
|
200
|
+
@code_lines[after_index.next..] || []
|
200
201
|
end
|
201
202
|
end
|
202
203
|
end
|
data/lib/dead_end/auto.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
3
|
require_relative "../dead_end/internals"
|
4
4
|
|
5
5
|
# Monkey patch kernel to ensure that all `require` calls call the same
|
@@ -27,7 +27,7 @@ module Kernel
|
|
27
27
|
if Pathname.new(file).absolute?
|
28
28
|
dead_end_original_require file
|
29
29
|
else
|
30
|
-
dead_end_original_require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
|
30
|
+
dead_end_original_require File.expand_path("../#{file}", Kernel.caller_locations(1, 1)[0].absolute_path)
|
31
31
|
end
|
32
32
|
rescue SyntaxError => e
|
33
33
|
DeadEnd.handle_error(e)
|
@@ -40,6 +40,7 @@ end
|
|
40
40
|
# am I doing something different?
|
41
41
|
class Object
|
42
42
|
private
|
43
|
+
|
43
44
|
def load(path, wrap = false)
|
44
45
|
Kernel.load(path, wrap)
|
45
46
|
rescue SyntaxError => e
|
@@ -52,53 +53,3 @@ class Object
|
|
52
53
|
DeadEnd.handle_error(e)
|
53
54
|
end
|
54
55
|
end
|
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
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module DeadEnd
|
3
4
|
# This class is responsible for taking a code block that exists
|
4
5
|
# at a far indentaion and then iteratively increasing the block
|
@@ -30,7 +31,7 @@ module DeadEnd
|
|
30
31
|
# end
|
31
32
|
#
|
32
33
|
class BlockExpand
|
33
|
-
def initialize(code_lines:
|
34
|
+
def initialize(code_lines:)
|
34
35
|
@code_lines = code_lines
|
35
36
|
end
|
36
37
|
|
@@ -43,7 +44,7 @@ module DeadEnd
|
|
43
44
|
end
|
44
45
|
|
45
46
|
def expand_indent(block)
|
46
|
-
|
47
|
+
AroundBlockScan.new(code_lines: @code_lines, block: block)
|
47
48
|
.skip(:hidden?)
|
48
49
|
.stop_after_kw
|
49
50
|
.scan_adjacent_indent
|
@@ -59,15 +60,15 @@ module DeadEnd
|
|
59
60
|
# Slurp up empties
|
60
61
|
if grab_empty
|
61
62
|
scan = AroundBlockScan.new(code_lines: @code_lines, block: scan.code_block)
|
62
|
-
.scan_while {|line| line.empty? || line.hidden? }
|
63
|
+
.scan_while { |line| line.empty? || line.hidden? }
|
63
64
|
end
|
64
65
|
|
65
66
|
new_block = scan.code_block
|
66
67
|
|
67
68
|
if block.lines == new_block.lines
|
68
|
-
|
69
|
+
nil
|
69
70
|
else
|
70
|
-
|
71
|
+
new_block
|
71
72
|
end
|
72
73
|
end
|
73
74
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DeadEnd
|
4
|
-
|
5
4
|
# Given a block, this method will capture surrounding
|
6
5
|
# code to give the user more context for the location of
|
7
6
|
# the problem.
|
@@ -26,7 +25,7 @@ module DeadEnd
|
|
26
25
|
class CaptureCodeContext
|
27
26
|
attr_reader :code_lines
|
28
27
|
|
29
|
-
def initialize(blocks
|
28
|
+
def initialize(blocks:, code_lines:)
|
30
29
|
@blocks = Array(blocks)
|
31
30
|
@code_lines = code_lines
|
32
31
|
@visible_lines = @blocks.map(&:visible_lines).flatten
|
@@ -45,13 +44,13 @@ module DeadEnd
|
|
45
44
|
@lines_to_output.uniq!
|
46
45
|
@lines_to_output.sort!
|
47
46
|
|
48
|
-
|
47
|
+
@lines_to_output
|
49
48
|
end
|
50
49
|
|
51
50
|
def capture_falling_indent(block)
|
52
51
|
AroundBlockScan.new(
|
53
52
|
block: block,
|
54
|
-
code_lines: @code_lines
|
53
|
+
code_lines: @code_lines
|
55
54
|
).on_falling_indent do |line|
|
56
55
|
@lines_to_output << line
|
57
56
|
end
|
@@ -67,50 +66,61 @@ module DeadEnd
|
|
67
66
|
@lines_to_output.concat(around_lines)
|
68
67
|
end
|
69
68
|
|
70
|
-
#
|
69
|
+
# When there is an invalid with a keyword
|
70
|
+
# right before an end, it's unclear where
|
71
|
+
# the correct code should be.
|
72
|
+
#
|
73
|
+
# Take this example:
|
74
|
+
#
|
75
|
+
# class Dog # 1
|
76
|
+
# def bark # 2
|
77
|
+
# puts "woof" # 3
|
78
|
+
# end # 4
|
79
|
+
#
|
80
|
+
# However due to https://github.com/zombocom/dead_end/issues/32
|
81
|
+
# the problem line will be identified as:
|
82
|
+
#
|
83
|
+
# ❯ class Dog # 1
|
84
|
+
#
|
85
|
+
# Because lines 2, 3, and 4 are technically valid code and are expanded
|
86
|
+
# first, deemed valid, and hidden. We need to un-hide the matching end
|
87
|
+
# line 4. Also work backwards and if there's a mis-matched keyword, show it
|
88
|
+
# too
|
71
89
|
def capture_last_end_same_indent(block)
|
72
90
|
start_index = block.visible_lines.first.index
|
73
91
|
lines = @code_lines[start_index..block.lines.last.index]
|
74
|
-
kw_end_lines = lines.select {|line| line.indent == block.current_indent && (line.is_end? || line.is_kw?) }
|
75
|
-
|
76
92
|
|
77
|
-
#
|
93
|
+
# Find first end with same indent
|
94
|
+
# (this would return line 4)
|
78
95
|
#
|
79
|
-
#
|
80
|
-
|
81
|
-
|
82
|
-
# was with the block instead of before it. To handle that
|
83
|
-
# special case, we can re-parse back through the internals of blocks
|
84
|
-
# and if they have mis-matched keywords and ends show the last one
|
85
|
-
end_lines = kw_end_lines.select(&:is_end?)
|
86
|
-
end_lines.each_with_index do |end_line, i|
|
87
|
-
start_index = i.zero? ? 0 : end_lines[i-1].index
|
88
|
-
end_index = end_line.index - 1
|
89
|
-
lines = @code_lines[start_index..end_index]
|
90
|
-
|
91
|
-
stop_next = false
|
92
|
-
kw_count = 0
|
93
|
-
end_count = 0
|
94
|
-
lines = lines.reverse.take_while do |line|
|
95
|
-
next false if stop_next
|
96
|
-
|
97
|
-
end_count += 1 if line.is_end?
|
98
|
-
kw_count += 1 if line.is_kw?
|
96
|
+
# end # 4
|
97
|
+
matching_end = lines.find { |line| line.indent == block.current_indent && line.is_end? }
|
98
|
+
return unless matching_end
|
99
99
|
|
100
|
-
|
101
|
-
true
|
102
|
-
end.reverse
|
100
|
+
@lines_to_output << matching_end
|
103
101
|
|
104
|
-
|
102
|
+
lines = @code_lines[start_index..matching_end.index]
|
105
103
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
104
|
+
# Work backwards from the end to
|
105
|
+
# see if there are mis-matched
|
106
|
+
# keyword/end pairs
|
107
|
+
#
|
108
|
+
# Return the first mis-matched keyword
|
109
|
+
# this would find line 2
|
110
|
+
#
|
111
|
+
# def bark # 2
|
112
|
+
# puts "woof" # 3
|
113
|
+
# end # 4
|
114
|
+
end_count = 0
|
115
|
+
kw_count = 0
|
116
|
+
kw_line = lines.reverse.detect do |line|
|
117
|
+
end_count += 1 if line.is_end?
|
118
|
+
kw_count += 1 if line.is_kw?
|
119
|
+
|
120
|
+
!kw_count.zero? && kw_count >= end_count
|
113
121
|
end
|
122
|
+
return unless kw_line
|
123
|
+
@lines_to_output << kw_line
|
114
124
|
end
|
115
125
|
end
|
116
126
|
end
|
data/lib/dead_end/code_block.rb
CHANGED
@@ -54,11 +54,11 @@ module DeadEnd
|
|
54
54
|
# populate an array with multiple code blocks then call `sort!`
|
55
55
|
# on it without having to specify the sorting criteria
|
56
56
|
def <=>(other)
|
57
|
-
out =
|
57
|
+
out = current_indent <=> other.current_indent
|
58
58
|
return out if out != 0
|
59
59
|
|
60
60
|
# Stable sort
|
61
|
-
|
61
|
+
starts_at <=> other.starts_at
|
62
62
|
end
|
63
63
|
|
64
64
|
def current_indent
|
@@ -71,7 +71,7 @@ module DeadEnd
|
|
71
71
|
|
72
72
|
def valid?
|
73
73
|
return @valid if @valid != UNSET
|
74
|
-
@valid = DeadEnd.valid?(
|
74
|
+
@valid = DeadEnd.valid?(to_s)
|
75
75
|
end
|
76
76
|
|
77
77
|
def to_s
|
@@ -39,7 +39,7 @@ module DeadEnd
|
|
39
39
|
#
|
40
40
|
# CodeFrontier#detect_invalid_blocks
|
41
41
|
class CodeFrontier
|
42
|
-
def initialize(code_lines:
|
42
|
+
def initialize(code_lines:)
|
43
43
|
@code_lines = code_lines
|
44
44
|
@frontier = []
|
45
45
|
@unvisited_lines = @code_lines.sort_by(&:indent_index)
|
@@ -66,7 +66,7 @@ module DeadEnd
|
|
66
66
|
|
67
67
|
# Returns a code block with the largest indentation possible
|
68
68
|
def pop
|
69
|
-
|
69
|
+
@frontier.pop
|
70
70
|
end
|
71
71
|
|
72
72
|
def next_indent_line
|
@@ -78,7 +78,7 @@ module DeadEnd
|
|
78
78
|
return true if @unvisited_lines.empty?
|
79
79
|
|
80
80
|
frontier_indent = @frontier.last.current_indent
|
81
|
-
unvisited_indent= next_indent_line.indent
|
81
|
+
unvisited_indent = next_indent_line.indent
|
82
82
|
|
83
83
|
if ENV["DEBUG"]
|
84
84
|
puts "```"
|
@@ -106,7 +106,7 @@ module DeadEnd
|
|
106
106
|
register_indent_block(block)
|
107
107
|
|
108
108
|
# Make sure we don't double expand, if a code block fully engulfs another code block, keep the bigger one
|
109
|
-
@frontier.reject! {|b|
|
109
|
+
@frontier.reject! { |b|
|
110
110
|
b.starts_at >= block.starts_at && b.ends_at <= block.ends_at
|
111
111
|
}
|
112
112
|
@frontier << block
|
data/lib/dead_end/code_line.rb
CHANGED
@@ -39,7 +39,7 @@ module DeadEnd
|
|
39
39
|
|
40
40
|
attr_reader :line, :index, :indent, :original_line
|
41
41
|
|
42
|
-
def initialize(line
|
42
|
+
def initialize(line:, index:)
|
43
43
|
@original_line = line.freeze
|
44
44
|
@line = @original_line
|
45
45
|
if line.strip.empty?
|
@@ -64,25 +64,25 @@ module DeadEnd
|
|
64
64
|
next unless lex.type == :on_kw
|
65
65
|
|
66
66
|
case lex.token
|
67
|
-
when
|
67
|
+
when "if", "unless", "while", "until"
|
68
68
|
# Only count if/unless when it's not a "trailing" if/unless
|
69
69
|
# https://github.com/ruby/ruby/blob/06b44f819eb7b5ede1ff69cecb25682b56a1d60c/lib/irb/ruby-lex.rb#L374-L375
|
70
|
-
kw_count += 1
|
71
|
-
when
|
70
|
+
kw_count += 1 unless lex.expr_label?
|
71
|
+
when "def", "case", "for", "begin", "class", "module", "do"
|
72
72
|
kw_count += 1
|
73
|
-
when
|
73
|
+
when "end"
|
74
74
|
end_count += 1
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
@is_comment = lex_array.detect {|lex| lex.type != :on_sp}&.type == :on_comment
|
78
|
+
@is_comment = lex_array.detect { |lex| lex.type != :on_sp }&.type == :on_comment
|
79
79
|
return if @is_comment
|
80
80
|
@is_kw = (kw_count - end_count) > 0
|
81
81
|
@is_end = (end_count - kw_count) > 0
|
82
82
|
@is_trailing_slash = lex_array.last.token == TRAILING_SLASH
|
83
83
|
end
|
84
84
|
|
85
|
-
|
85
|
+
alias_method :original, :original_line
|
86
86
|
|
87
87
|
def trailing_slash?
|
88
88
|
@is_trailing_slash
|
@@ -92,8 +92,8 @@ module DeadEnd
|
|
92
92
|
@indent_index ||= [indent, index]
|
93
93
|
end
|
94
94
|
|
95
|
-
def <=>(
|
96
|
-
|
95
|
+
def <=>(other)
|
96
|
+
index <=> other.index
|
97
97
|
end
|
98
98
|
|
99
99
|
def is_comment?
|
@@ -133,7 +133,7 @@ module DeadEnd
|
|
133
133
|
def line_number
|
134
134
|
index + 1
|
135
135
|
end
|
136
|
-
|
136
|
+
alias_method :number, :line_number
|
137
137
|
|
138
138
|
def not_empty?
|
139
139
|
!empty?
|
@@ -144,7 +144,7 @@ module DeadEnd
|
|
144
144
|
end
|
145
145
|
|
146
146
|
def to_s
|
147
|
-
|
147
|
+
line
|
148
148
|
end
|
149
149
|
end
|
150
150
|
end
|
data/lib/dead_end/code_search.rb
CHANGED
@@ -25,14 +25,21 @@ module DeadEnd
|
|
25
25
|
# # => ["def lol\n"]
|
26
26
|
#
|
27
27
|
class CodeSearch
|
28
|
-
private
|
29
|
-
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :frontier
|
31
|
+
|
32
|
+
public
|
33
|
+
|
34
|
+
public
|
35
|
+
|
36
|
+
attr_reader :invalid_blocks, :record_dir, :code_lines
|
30
37
|
|
31
38
|
def initialize(source, record_dir: ENV["DEAD_END_RECORD_DIR"] || ENV["DEBUG"] ? "tmp" : nil)
|
32
39
|
@source = source
|
33
40
|
if record_dir
|
34
|
-
@time = Time.now.strftime(
|
35
|
-
@record_dir = Pathname(record_dir).join(@time).tap {|p| p.mkpath }
|
41
|
+
@time = Time.now.strftime("%Y-%m-%d-%H-%M-%s-%N")
|
42
|
+
@record_dir = Pathname(record_dir).join(@time).tap { |p| p.mkpath }
|
36
43
|
@write_count = 0
|
37
44
|
end
|
38
45
|
code_lines = source.lines.map.with_index do |line, i|
|
@@ -43,7 +50,7 @@ module DeadEnd
|
|
43
50
|
|
44
51
|
@frontier = CodeFrontier.new(code_lines: @code_lines)
|
45
52
|
@invalid_blocks = []
|
46
|
-
@name_tick = Hash.new {|hash, k| hash[k] = 0 }
|
53
|
+
@name_tick = Hash.new { |hash, k| hash[k] = 0 }
|
47
54
|
@tick = 0
|
48
55
|
@block_expand = BlockExpand.new(code_lines: code_lines)
|
49
56
|
@parse_blocks_from_indent_line = ParseBlocksFromIndentLine.new(code_lines: @code_lines)
|
@@ -51,13 +58,13 @@ module DeadEnd
|
|
51
58
|
|
52
59
|
# Used for debugging
|
53
60
|
def record(block:, name: "record")
|
54
|
-
return
|
61
|
+
return unless @record_dir
|
55
62
|
@name_tick[name] += 1
|
56
63
|
filename = "#{@write_count += 1}-#{name}-#{@name_tick[name]}.txt"
|
57
64
|
if ENV["DEBUG"]
|
58
65
|
puts "\n\n==== #{filename} ===="
|
59
66
|
puts "\n```#{block.starts_at}:#{block.ends_at}"
|
60
|
-
puts
|
67
|
+
puts block.to_s
|
61
68
|
puts "```"
|
62
69
|
puts " block indent: #{block.current_indent}"
|
63
70
|
end
|
@@ -65,25 +72,21 @@ module DeadEnd
|
|
65
72
|
display = DisplayInvalidBlocks.new(
|
66
73
|
blocks: block,
|
67
74
|
terminal: false,
|
68
|
-
code_lines: @code_lines
|
75
|
+
code_lines: @code_lines
|
69
76
|
)
|
70
|
-
f.write(display.indent
|
77
|
+
f.write(display.indent(display.code_with_lines))
|
71
78
|
end
|
72
79
|
end
|
73
80
|
|
74
|
-
def push(block, name:
|
81
|
+
def push(block, name:)
|
75
82
|
record(block: block, name: name)
|
76
83
|
|
77
|
-
if block.valid?
|
78
|
-
|
79
|
-
frontier << block
|
80
|
-
else
|
81
|
-
frontier << block
|
82
|
-
end
|
84
|
+
block.mark_invisible if block.valid?
|
85
|
+
frontier << block
|
83
86
|
end
|
84
87
|
|
85
88
|
# Removes the block without putting it back in the frontier
|
86
|
-
def sweep(block:, name:
|
89
|
+
def sweep(block:, name:)
|
87
90
|
record(block: block, name: name)
|
88
91
|
|
89
92
|
block.lines.each(&:mark_invisible)
|
@@ -149,8 +152,8 @@ module DeadEnd
|
|
149
152
|
end
|
150
153
|
end
|
151
154
|
|
152
|
-
@invalid_blocks.concat(frontier.detect_invalid_blocks
|
153
|
-
@invalid_blocks.sort_by! {|block| block.starts_at }
|
155
|
+
@invalid_blocks.concat(frontier.detect_invalid_blocks)
|
156
|
+
@invalid_blocks.sort_by! { |block| block.starts_at }
|
154
157
|
self
|
155
158
|
end
|
156
159
|
end
|
@@ -23,10 +23,10 @@ module DeadEnd
|
|
23
23
|
TERMINAL_HIGHLIGHT = "\e[1;3m" # Bold, italics
|
24
24
|
TERMINAL_END = "\e[0m"
|
25
25
|
|
26
|
-
def initialize(lines
|
26
|
+
def initialize(lines:, highlight_lines: [], terminal: false)
|
27
27
|
@lines = Array(lines).sort
|
28
28
|
@terminal = terminal
|
29
|
-
@highlight_line_hash = Array(highlight_lines).each_with_object({}) {|line, h| h[line] = true
|
29
|
+
@highlight_line_hash = Array(highlight_lines).each_with_object({}) { |line, h| h[line] = true }
|
30
30
|
@digit_count = @lines.last&.line_number.to_s.length
|
31
31
|
end
|
32
32
|
|
@@ -48,12 +48,12 @@ module DeadEnd
|
|
48
48
|
end.join
|
49
49
|
end
|
50
50
|
|
51
|
-
private def format(contents
|
52
|
-
string =
|
53
|
-
if highlight
|
54
|
-
|
51
|
+
private def format(contents:, number:, empty:, highlight: false)
|
52
|
+
string = +""
|
53
|
+
string << if highlight
|
54
|
+
"❯ "
|
55
55
|
else
|
56
|
-
|
56
|
+
" "
|
57
57
|
end
|
58
58
|
|
59
59
|
string << number.rjust(@digit_count).to_s
|
@@ -8,7 +8,7 @@ module DeadEnd
|
|
8
8
|
class DisplayInvalidBlocks
|
9
9
|
attr_reader :filename
|
10
10
|
|
11
|
-
def initialize(code_lines
|
11
|
+
def initialize(code_lines:, blocks:, io: $stderr, filename: nil, terminal: false, invalid_obj: WhoDisSyntaxError::Null.new)
|
12
12
|
@terminal = terminal
|
13
13
|
@filename = filename
|
14
14
|
@io = io
|
@@ -78,22 +78,21 @@ module DeadEnd
|
|
78
78
|
<<~EOM
|
79
79
|
DeadEnd: Unmatched `}` character detected
|
80
80
|
|
81
|
-
This code has an unmatched `}`. Ensure that opening
|
81
|
+
This code has an unmatched `}`. Ensure that opening curly braces are
|
82
82
|
closed: `{ }`.
|
83
83
|
EOM
|
84
84
|
else
|
85
|
-
"DeadEnd: Unmatched
|
85
|
+
"DeadEnd: Unmatched `#{@invalid_obj.unmatched_symbol}` detected"
|
86
86
|
end
|
87
87
|
end
|
88
|
-
|
89
88
|
end
|
90
89
|
|
91
90
|
def indent(string, with: " ")
|
92
|
-
string.each_line.map {|l| with
|
91
|
+
string.each_line.map { |l| with + l }.join
|
93
92
|
end
|
94
93
|
|
95
94
|
def code_block
|
96
|
-
string =
|
95
|
+
string = +""
|
97
96
|
string << code_with_context
|
98
97
|
string
|
99
98
|
end
|
@@ -107,7 +106,7 @@ module DeadEnd
|
|
107
106
|
DisplayCodeWithLineNumbers.new(
|
108
107
|
lines: lines,
|
109
108
|
terminal: @terminal,
|
110
|
-
highlight_lines: @invalid_lines
|
109
|
+
highlight_lines: @invalid_lines
|
111
110
|
).call
|
112
111
|
end
|
113
112
|
|
@@ -115,7 +114,7 @@ module DeadEnd
|
|
115
114
|
DisplayCodeWithLineNumbers.new(
|
116
115
|
lines: @code_lines.select(&:visible?),
|
117
116
|
terminal: @terminal,
|
118
|
-
highlight_lines: @invalid_lines
|
117
|
+
highlight_lines: @invalid_lines
|
119
118
|
).call
|
120
119
|
end
|
121
120
|
end
|
data/lib/dead_end/fyi.rb
CHANGED
@@ -3,9 +3,13 @@
|
|
3
3
|
module DeadEnd
|
4
4
|
# Takes in a source, and returns blocks containing each heredoc
|
5
5
|
class HeredocBlockParse
|
6
|
-
private
|
6
|
+
private
|
7
7
|
|
8
|
-
|
8
|
+
attr_reader :code_lines, :lex
|
9
|
+
|
10
|
+
public
|
11
|
+
|
12
|
+
def initialize(source:, code_lines:)
|
9
13
|
@code_lines = code_lines
|
10
14
|
@lex = LexAll.new(source: source)
|
11
15
|
end
|
data/lib/dead_end/internals.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
#
|
3
4
|
# This is the top level file, but is moved to `internals`
|
4
5
|
# so the top level file can instead enable the "automatic" behavior
|
5
6
|
|
6
7
|
require_relative "version"
|
7
8
|
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
9
|
+
require "tmpdir"
|
10
|
+
require "stringio"
|
11
|
+
require "pathname"
|
12
|
+
require "ripper"
|
13
|
+
require "timeout"
|
13
14
|
|
14
15
|
module DeadEnd
|
15
16
|
class Error < StandardError; end
|
@@ -17,27 +18,27 @@ module DeadEnd
|
|
17
18
|
TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 5).to_i
|
18
19
|
|
19
20
|
def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT)
|
20
|
-
raise e
|
21
|
+
raise e unless e.message.include?("end-of-input")
|
21
22
|
|
22
23
|
filename = e.message.split(":").first
|
23
24
|
|
24
25
|
$stderr.sync = true
|
25
|
-
|
26
|
+
warn "Run `$ dead_end #{filename}` for more options\n"
|
26
27
|
|
27
28
|
if search_source_on_error
|
28
|
-
|
29
|
+
call(
|
29
30
|
source: Pathname(filename).read,
|
30
31
|
filename: filename,
|
31
|
-
terminal: true
|
32
|
+
terminal: true
|
32
33
|
)
|
33
34
|
end
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
warn ""
|
37
|
+
warn ""
|
37
38
|
raise e
|
38
39
|
end
|
39
40
|
|
40
|
-
def self.call(source
|
41
|
+
def self.call(source:, filename:, terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
|
41
42
|
search = nil
|
42
43
|
Timeout.timeout(timeout) do
|
43
44
|
record_dir ||= ENV["DEBUG"] ? "tmp" : nil
|
@@ -82,13 +83,13 @@ module DeadEnd
|
|
82
83
|
# ) # => true
|
83
84
|
#
|
84
85
|
# DeadEnd.valid?(code_lines) # => false
|
85
|
-
def self.valid_without?(without_lines
|
86
|
+
def self.valid_without?(without_lines:, code_lines:)
|
86
87
|
lines = code_lines - Array(without_lines).flatten
|
87
88
|
|
88
89
|
if lines.empty?
|
89
|
-
|
90
|
+
true
|
90
91
|
else
|
91
|
-
|
92
|
+
valid?(lines)
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
@@ -137,7 +138,6 @@ module DeadEnd
|
|
137
138
|
!invalid?(source)
|
138
139
|
end
|
139
140
|
|
140
|
-
|
141
141
|
def self.invalid_type(source)
|
142
142
|
WhoDisSyntaxError.new(source).call
|
143
143
|
end
|
data/lib/dead_end/lex_all.rb
CHANGED
@@ -8,20 +8,20 @@ module DeadEnd
|
|
8
8
|
class LexAll
|
9
9
|
include Enumerable
|
10
10
|
|
11
|
-
def initialize(source:
|
11
|
+
def initialize(source:)
|
12
12
|
@lex = Ripper.lex(source)
|
13
|
-
lineno = @lex.last
|
13
|
+
lineno = @lex.last.first.first + 1
|
14
14
|
source_lines = source.lines
|
15
15
|
last_lineno = source_lines.count
|
16
16
|
|
17
17
|
until lineno >= last_lineno
|
18
|
-
lines = source_lines[lineno
|
18
|
+
lines = source_lines[lineno..]
|
19
19
|
|
20
|
-
@lex.concat(Ripper.lex(lines.join,
|
21
|
-
lineno = @lex.last
|
20
|
+
@lex.concat(Ripper.lex(lines.join, "-", lineno + 1))
|
21
|
+
lineno = @lex.last.first.first + 1
|
22
22
|
end
|
23
23
|
|
24
|
-
@lex.map! {|(line, _), type, token, state| LexValue.new(line,
|
24
|
+
@lex.map! { |(line, _), type, token, state| LexValue.new(line, type, token, state) }
|
25
25
|
end
|
26
26
|
|
27
27
|
def each
|
@@ -49,7 +49,7 @@ module DeadEnd
|
|
49
49
|
class LexValue
|
50
50
|
attr_reader :line, :type, :token, :state
|
51
51
|
|
52
|
-
def initialize(line,
|
52
|
+
def initialize(line, type, token, state)
|
53
53
|
@line = line
|
54
54
|
@type = type
|
55
55
|
@token = token
|
@@ -29,7 +29,7 @@ module DeadEnd
|
|
29
29
|
class ParseBlocksFromIndentLine
|
30
30
|
attr_reader :code_lines
|
31
31
|
|
32
|
-
def initialize(code_lines:
|
32
|
+
def initialize(code_lines:)
|
33
33
|
@code_lines = code_lines
|
34
34
|
end
|
35
35
|
|
@@ -38,7 +38,7 @@ module DeadEnd
|
|
38
38
|
scan = AroundBlockScan.new(code_lines: code_lines, block: CodeBlock.new(lines: target_line))
|
39
39
|
.skip(:empty?)
|
40
40
|
.skip(:hidden?)
|
41
|
-
.scan_while {|line| line.indent >= target_line.indent }
|
41
|
+
.scan_while { |line| line.indent >= target_line.indent }
|
42
42
|
|
43
43
|
neighbors = scan.code_block.lines
|
44
44
|
|
@@ -53,4 +53,3 @@ module DeadEnd
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
@@ -27,7 +27,7 @@ module DeadEnd
|
|
27
27
|
@trailing_lines = []
|
28
28
|
@code_lines.select(&:trailing_slash?).each do |trailing|
|
29
29
|
stop_next = false
|
30
|
-
lines = @code_lines[trailing.index
|
30
|
+
lines = @code_lines[trailing.index..].take_while do |line|
|
31
31
|
next false if stop_next
|
32
32
|
|
33
33
|
if !line.trailing_slash?
|
@@ -47,7 +47,7 @@ module DeadEnd
|
|
47
47
|
lines.each(&:mark_invisible)
|
48
48
|
end
|
49
49
|
|
50
|
-
|
50
|
+
@code_lines_dup
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
data/lib/dead_end/version.rb
CHANGED
@@ -9,8 +9,13 @@ module DeadEnd
|
|
9
9
|
# # => :missing_end
|
10
10
|
class WhoDisSyntaxError < Ripper
|
11
11
|
class Null
|
12
|
-
def error_symbol
|
13
|
-
|
12
|
+
def error_symbol
|
13
|
+
:missing_end
|
14
|
+
end
|
15
|
+
|
16
|
+
def unmatched_symbol
|
17
|
+
:end
|
18
|
+
end
|
14
19
|
end
|
15
20
|
attr_reader :error, :run_once
|
16
21
|
|
@@ -42,22 +47,25 @@ module DeadEnd
|
|
42
47
|
end
|
43
48
|
|
44
49
|
def on_parse_error(msg)
|
50
|
+
return if @error_symbol && @unmatched_symbol
|
51
|
+
|
45
52
|
@error = msg
|
46
53
|
@unmatched_symbol = :unknown
|
47
54
|
|
48
|
-
|
55
|
+
case @error
|
56
|
+
when /unexpected end-of-input/
|
49
57
|
@error_symbol = :missing_end
|
50
|
-
|
51
|
-
@error_symbol = :unmatched_syntax
|
58
|
+
when /expecting end-of-input/
|
52
59
|
@unmatched_symbol = :end
|
53
|
-
elsif @error.match?(/unexpected `end'/) || # Ruby 2.7 & 3.0
|
54
|
-
@error.match?(/unexpected end,/) || # Ruby 2.6
|
55
|
-
@error.match?(/unexpected keyword_end/) # Ruby 2.5
|
56
|
-
|
57
60
|
@error_symbol = :unmatched_syntax
|
61
|
+
when /unexpected .* expecting '(?<unmatched_symbol>.*)'/
|
62
|
+
@unmatched_symbol = $1.to_sym if $1
|
63
|
+
@error_symbol = :unmatched_syntax
|
64
|
+
when /unexpected `end'/, # Ruby 2.7 and 3.0
|
65
|
+
/unexpected end/, # Ruby 2.6
|
66
|
+
/unexpected keyword_end/i # Ruby 2.5
|
58
67
|
|
59
|
-
|
60
|
-
@unmatched_symbol = match[:unmatched_symbol].to_sym if match
|
68
|
+
@error_symbol = :unmatched_syntax
|
61
69
|
else
|
62
70
|
@error_symbol = :unknown
|
63
71
|
end
|
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.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schneems
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-08 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
|
@@ -23,7 +23,7 @@ files:
|
|
23
23
|
- ".github/workflows/check_changelog.yml"
|
24
24
|
- ".gitignore"
|
25
25
|
- ".rspec"
|
26
|
-
- ".
|
26
|
+
- ".standard.yml"
|
27
27
|
- CHANGELOG.md
|
28
28
|
- CODE_OF_CONDUCT.md
|
29
29
|
- Gemfile
|
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
75
|
- !ruby/object:Gem::Version
|
76
76
|
version: '0'
|
77
77
|
requirements: []
|
78
|
-
rubygems_version: 3.
|
78
|
+
rubygems_version: 3.2.22
|
79
79
|
signing_key:
|
80
80
|
specification_version: 4
|
81
81
|
summary: Find syntax errors in your source in a snap
|