dead_end 1.2.0 → 3.0.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 +9 -0
- data/.github/workflows/check_changelog.yml +14 -7
- data/.standard.yml +1 -1
- data/CHANGELOG.md +29 -0
- data/Gemfile.lock +2 -2
- data/README.md +89 -21
- data/exe/dead_end +3 -66
- data/lib/dead_end/around_block_scan.rb +6 -9
- data/lib/dead_end/auto.rb +1 -21
- data/lib/dead_end/capture_code_context.rb +123 -16
- data/lib/dead_end/clean_document.rb +313 -0
- data/lib/dead_end/cli.rb +118 -0
- data/lib/dead_end/code_block.rb +18 -2
- data/lib/dead_end/code_frontier.rb +53 -16
- data/lib/dead_end/code_line.rb +159 -76
- data/lib/dead_end/code_search.rb +24 -37
- data/lib/dead_end/display_code_with_line_numbers.rb +0 -1
- data/lib/dead_end/display_invalid_blocks.rb +41 -78
- data/lib/dead_end/explain_syntax.rb +103 -0
- data/lib/dead_end/left_right_lex_count.rb +157 -0
- data/lib/dead_end/lex_all.rb +11 -27
- data/lib/dead_end/lex_value.rb +62 -0
- data/lib/dead_end/parse_blocks_from_indent_line.rb +1 -1
- data/lib/dead_end/ripper_errors.rb +30 -0
- data/lib/dead_end/version.rb +1 -1
- data/lib/dead_end.rb +145 -1
- metadata +8 -7
- data/lib/dead_end/fyi.rb +0 -6
- data/lib/dead_end/heredoc_block_parse.rb +0 -34
- data/lib/dead_end/internals.rb +0 -158
- data/lib/dead_end/trailing_slash_join.rb +0 -53
- data/lib/dead_end/who_dis_syntax_error.rb +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72373898d38363c0ea0c3cec7fe2747399724ce55dae4eca1ce57b8f59dfc767
|
4
|
+
data.tar.gz: 9e99a5fddb8a054b839aef4fe1c2b87792bc9b4161bf5a6689156f2999dca935
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7312a010846453f222bbb8717738831f9a2e23572eace345ed7738f00429c126d1a4760548ad9e7552237aee057b5c5bae90b7a0fe197144c3a9dc9d3db2ab0b
|
7
|
+
data.tar.gz: 0db2b356ab237f01eda6dc5ebeac0156c317d94535c2b59d099f766e1716d5d6f6b429edcc65bf439dea1bc15e1866f4d66a41cd28d927a29a666f54f54ee804
|
data/.circleci/config.yml
CHANGED
@@ -13,6 +13,14 @@ references:
|
|
13
13
|
command: bundle exec standardrb
|
14
14
|
|
15
15
|
jobs:
|
16
|
+
"ruby-2-5":
|
17
|
+
docker:
|
18
|
+
- image: circleci/ruby:2.5
|
19
|
+
steps:
|
20
|
+
- checkout
|
21
|
+
- ruby/install-deps
|
22
|
+
- <<: *unit
|
23
|
+
|
16
24
|
"ruby-2-6":
|
17
25
|
docker:
|
18
26
|
- image: circleci/ruby:2.6
|
@@ -49,6 +57,7 @@ workflows:
|
|
49
57
|
version: 2
|
50
58
|
build:
|
51
59
|
jobs:
|
60
|
+
- "ruby-2-5"
|
52
61
|
- "ruby-2-6"
|
53
62
|
- "ruby-2-7"
|
54
63
|
- "ruby-3-0"
|
@@ -1,13 +1,20 @@
|
|
1
1
|
name: Check Changelog
|
2
2
|
|
3
3
|
on:
|
4
|
-
|
5
|
-
|
4
|
+
pull_request:
|
5
|
+
types: [opened, reopened, edited, labeled, unlabeled, synchronize]
|
6
|
+
|
6
7
|
jobs:
|
7
|
-
|
8
|
+
check-changelog:
|
8
9
|
runs-on: ubuntu-latest
|
10
|
+
if: |
|
11
|
+
!contains(github.event.pull_request.body, '[skip changelog]') &&
|
12
|
+
!contains(github.event.pull_request.body, '[changelog skip]') &&
|
13
|
+
!contains(github.event.pull_request.body, '[skip ci]') &&
|
14
|
+
!contains(github.event.pull_request.labels.*.name, 'skip changelog')
|
9
15
|
steps:
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
16
|
+
- uses: actions/checkout@v2.3.5
|
17
|
+
- name: Check that CHANGELOG is touched
|
18
|
+
run: |
|
19
|
+
git fetch origin ${{ github.base_ref }} --depth 1 && \
|
20
|
+
git diff remotes/origin/${{ github.base_ref }} --name-only | grep CHANGELOG.md
|
data/.standard.yml
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby_version: 2.
|
1
|
+
ruby_version: 2.5.9
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,34 @@
|
|
1
1
|
## HEAD (unreleased)
|
2
2
|
|
3
|
+
## 3.0.0
|
4
|
+
|
5
|
+
- [Breaking] Remove previously deprecated `require "dead_end/fyi"` interface (https://github.com/zombocom/dead_end/pull/94)
|
6
|
+
- Fix double output bug (https://github.com/zombocom/dead_end/pull/99)
|
7
|
+
- Fix bug causing poor results (fix #95, fix #88) (https://github.com/zombocom/dead_end/pull/96)
|
8
|
+
- DeadEnd is now fired on EVERY syntax error (https://github.com/zombocom/dead_end/pull/94)
|
9
|
+
- Output format changes:
|
10
|
+
- Parse errors emitted per-block rather than for the whole document (https://github.com/zombocom/dead_end/pull/94)
|
11
|
+
- The "banner" is now based on lexical analysis rather than parser regex (fix #68, fix #87) (https://github.com/zombocom/dead_end/pull/96)
|
12
|
+
|
13
|
+
## 2.0.2
|
14
|
+
|
15
|
+
- Don't print terminal color codes when output is not tty (https://github.com/zombocom/dead_end/pull/91)
|
16
|
+
|
17
|
+
## 2.0.1
|
18
|
+
|
19
|
+
- Reintroduce Ruby 2.5 support (https://github.com/zombocom/dead_end/pull/90)
|
20
|
+
- Support naked braces/brackets/parens, invert labels on banner (https://github.com/zombocom/dead_end/pull/89)
|
21
|
+
- Handle mismatched end when using rescue without begin (https://github.com/zombocom/dead_end/pull/83)
|
22
|
+
- CLI returns non-zero exit code when syntax error is found (https://github.com/zombocom/dead_end/pull/86)
|
23
|
+
- Let -v respond with gem version instead of 'unknown' (https://github.com/zombocom/dead_end/pull/82)
|
24
|
+
|
25
|
+
## 2.0.0
|
26
|
+
|
27
|
+
- Support "endless" oneline method definitions for Ruby 3+ (https://github.com/zombocom/dead_end/pull/80)
|
28
|
+
- Reduce timeout to 1 second (https://github.com/zombocom/dead_end/pull/79)
|
29
|
+
- Logically consecutive lines (such as chained methods are now joined) (https://github.com/zombocom/dead_end/pull/78)
|
30
|
+
- Output improvement for cases where the only line is an single `end` (https://github.com/zombocom/dead_end/pull/78)
|
31
|
+
|
3
32
|
## 1.2.0
|
4
33
|
|
5
34
|
- Output improvements via less greedy unmatched kw capture https://github.com/zombocom/dead_end/pull/73
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,19 +2,14 @@
|
|
2
2
|
|
3
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
|
-
|
6
|
-
|
7
|
-
This code has an unmatched `end`. Ensure that all `end` lines
|
8
|
-
in your code have a matching syntax keyword (`def`, `do`, etc.)
|
9
|
-
and that you don't have any extra `end` lines.
|
10
|
-
|
11
|
-
file: path/to/dog.rb
|
12
|
-
simplified:
|
5
|
+
```
|
6
|
+
Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?
|
13
7
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
8
|
+
1 class Dog
|
9
|
+
❯ 2 defbark
|
10
|
+
❯ 4 end
|
11
|
+
5 end
|
12
|
+
```
|
18
13
|
|
19
14
|
## Installation in your codebase
|
20
15
|
|
@@ -52,34 +47,99 @@ This gives you the CLI command `$ dead_end` for more info run `$ dead_end --help
|
|
52
47
|
|
53
48
|
## What syntax errors does it handle?
|
54
49
|
|
50
|
+
Dead end will fire against all syntax errors and can isolate any syntax error. In addition, dead_end attempts to produce human readable descriptions of what needs to be done to resolve the issue. For example:
|
51
|
+
|
55
52
|
- Missing `end`:
|
56
53
|
|
54
|
+
<!--
|
57
55
|
```ruby
|
58
56
|
class Dog
|
59
57
|
def bark
|
60
58
|
puts "bark"
|
61
|
-
|
62
|
-
def woof
|
63
|
-
puts "woof"
|
64
|
-
end
|
65
59
|
end
|
66
|
-
# => scratch.rb:8: syntax error, unexpected end-of-input, expecting `end'
|
67
60
|
```
|
61
|
+
-->
|
68
62
|
|
69
|
-
|
63
|
+
```
|
64
|
+
Unmatched keyword, missing `end' ?
|
70
65
|
|
66
|
+
❯ 1 class Dog
|
67
|
+
❯ 2 def bark
|
68
|
+
❯ 4 end
|
69
|
+
```
|
70
|
+
|
71
|
+
- Missing keyword
|
72
|
+
<!--
|
71
73
|
```ruby
|
72
74
|
class Dog
|
73
75
|
def speak
|
74
|
-
@sounds.each |sound|
|
76
|
+
@sounds.each |sound|
|
75
77
|
puts sound
|
76
78
|
end
|
77
79
|
end
|
78
80
|
end
|
79
|
-
# => scratch.rb:7: syntax error, unexpected `end', expecting end-of-input
|
80
81
|
```
|
82
|
+
-->
|
81
83
|
|
82
|
-
|
84
|
+
```
|
85
|
+
Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?
|
86
|
+
|
87
|
+
1 class Dog
|
88
|
+
2 def speak
|
89
|
+
❯ 3 @sounds.each |sound|
|
90
|
+
❯ 5 end
|
91
|
+
6 end
|
92
|
+
7 end
|
93
|
+
```
|
94
|
+
|
95
|
+
- Missing pair characters (like `{}`, `[]`, `()` , or `|<var>|`)
|
96
|
+
<!--
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
class Dog
|
100
|
+
def speak(sound
|
101
|
+
puts sound
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
-->
|
106
|
+
|
107
|
+
```
|
108
|
+
Unmatched `(', missing `)' ?
|
109
|
+
|
110
|
+
1 class Dog
|
111
|
+
❯ 2 def speak(sound
|
112
|
+
❯ 4 end
|
113
|
+
5 end
|
114
|
+
```
|
115
|
+
|
116
|
+
- Any ambiguous or unknown errors will be annotated by the original ripper error output:
|
117
|
+
|
118
|
+
<!--
|
119
|
+
class Dog
|
120
|
+
def meals_last_month
|
121
|
+
puts 3 *
|
122
|
+
end
|
123
|
+
end
|
124
|
+
-->
|
125
|
+
|
126
|
+
```
|
127
|
+
syntax error, unexpected end-of-input
|
128
|
+
|
129
|
+
1 class Dog
|
130
|
+
2 def meals_last_month
|
131
|
+
❯ 3 puts 3 *
|
132
|
+
4 end
|
133
|
+
5 end
|
134
|
+
```
|
135
|
+
|
136
|
+
## How is it better than `ruby -wc`?
|
137
|
+
|
138
|
+
Ruby allows you to syntax check a file with warnings using `ruby -wc`. This emits a parser error instead of a human focused error. Ruby's parse errors attempt to narrow down the location and can tell you if there is a glaring indentation error involving `end`.
|
139
|
+
|
140
|
+
The `dead_end` algorithm doesn't just guess at the location of syntax errors, it re-parses the document to prove that it captured them.
|
141
|
+
|
142
|
+
This library focuses on the human side of syntax errors. It cares less about why the document could not be parsed (computer problem) and more on what the programmer needs (human problem) to fix the problem.
|
83
143
|
|
84
144
|
## Sounds cool, but why isn't this baked into Ruby directly?
|
85
145
|
|
@@ -105,6 +165,14 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
105
165
|
|
106
166
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
107
167
|
|
168
|
+
### How to debug changes to output display
|
169
|
+
|
170
|
+
You can see changes to output against a variety of invalid code by running specs and using the `DEBUG_DISPLAY=1` environment variable. For example:
|
171
|
+
|
172
|
+
```
|
173
|
+
$ DEBUG_DISPLAY=1 be rspec spec/ --format=failures
|
174
|
+
```
|
175
|
+
|
108
176
|
## Contributing
|
109
177
|
|
110
178
|
Bug reports and pull requests are welcome on GitHub at https://github.com/zombocom/dead_end. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/zombocom/dead_end/blob/master/CODE_OF_CONDUCT.md).
|
data/exe/dead_end
CHANGED
@@ -1,70 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require "pathname"
|
4
|
-
require "optparse"
|
5
3
|
require_relative "../lib/dead_end"
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
parser = OptionParser.new do |opts|
|
12
|
-
opts.banner = <<~EOM
|
13
|
-
Usage: dead_end <file> [options]
|
14
|
-
|
15
|
-
Parses a ruby source file and searches for syntax error(s) such as
|
16
|
-
unexpected `end', expecting end-of-input.
|
17
|
-
|
18
|
-
Example:
|
19
|
-
|
20
|
-
$ dead_end dog.rb
|
21
|
-
|
22
|
-
# ...
|
23
|
-
|
24
|
-
❯ 10 defdog
|
25
|
-
❯ 15 end
|
26
|
-
❯ 16
|
27
|
-
|
28
|
-
Env options:
|
29
|
-
|
30
|
-
DEAD_END_RECORD_DIR=<dir>
|
31
|
-
|
32
|
-
When enabled, records the steps used to search for a syntax error to the
|
33
|
-
given directory
|
34
|
-
|
35
|
-
Options:
|
36
|
-
EOM
|
37
|
-
|
38
|
-
opts.on("--help", "Help - displays this message") do |v|
|
39
|
-
puts opts
|
40
|
-
exit
|
41
|
-
end
|
42
|
-
|
43
|
-
opts.on("--record <dir>", "When enabled, records the steps used to search for a syntax error to the given directory") do |v|
|
44
|
-
options[:record_dir] = v
|
45
|
-
end
|
46
|
-
|
47
|
-
opts.on("--no-terminal", "Disable terminal highlighting") do |v|
|
48
|
-
options[:terminal] = false
|
49
|
-
end
|
50
|
-
end
|
51
|
-
parser.parse!
|
52
|
-
|
53
|
-
file = ARGV[0]
|
54
|
-
|
55
|
-
if file.nil? || file.empty?
|
56
|
-
# Display help if raw command
|
57
|
-
parser.parse! %w[--help]
|
58
|
-
end
|
59
|
-
|
60
|
-
file = Pathname(file)
|
61
|
-
options[:record_dir] = "tmp" if ENV["DEBUG"]
|
62
|
-
|
63
|
-
warn "Record dir: #{options[:record_dir]}" if options[:record_dir]
|
64
|
-
|
65
|
-
DeadEnd.call(
|
66
|
-
source: file.read,
|
67
|
-
filename: file.expand_path,
|
68
|
-
terminal: options[:terminal],
|
69
|
-
record_dir: options[:record_dir]
|
70
|
-
)
|
5
|
+
DeadEnd::Cli.new(
|
6
|
+
argv: ARGV
|
7
|
+
).call
|
@@ -9,10 +9,10 @@ module DeadEnd
|
|
9
9
|
#
|
10
10
|
# Example:
|
11
11
|
#
|
12
|
-
# def dog
|
13
|
-
# puts "bark"
|
14
|
-
# puts "bark"
|
15
|
-
# end
|
12
|
+
# def dog # 1
|
13
|
+
# puts "bark" # 2
|
14
|
+
# puts "bark" # 3
|
15
|
+
# end # 4
|
16
16
|
#
|
17
17
|
# scan = AroundBlockScan.new(
|
18
18
|
# code_lines: code_lines
|
@@ -22,7 +22,7 @@ module DeadEnd
|
|
22
22
|
# scan.scan_while { true }
|
23
23
|
#
|
24
24
|
# puts scan.before_index # => 0
|
25
|
-
# puts scan.after_index
|
25
|
+
# puts scan.after_index # => 3
|
26
26
|
#
|
27
27
|
# Contents can also be filtered using AroundBlockScan#skip
|
28
28
|
#
|
@@ -109,8 +109,6 @@ module DeadEnd
|
|
109
109
|
kw_count = 0
|
110
110
|
end_count = 0
|
111
111
|
after_lines.each do |line|
|
112
|
-
# puts "line: #{line.number} #{line.original_line}, indent: #{line.indent}, #{line.empty?} #{line.indent == @orig_indent}"
|
113
|
-
|
114
112
|
next if line.empty?
|
115
113
|
break if line.indent < @orig_indent
|
116
114
|
next if line.indent != @orig_indent
|
@@ -124,7 +122,6 @@ module DeadEnd
|
|
124
122
|
|
125
123
|
lines << line
|
126
124
|
end
|
127
|
-
lines.select! { |line| !line.is_comment? }
|
128
125
|
|
129
126
|
lines
|
130
127
|
end
|
@@ -197,7 +194,7 @@ module DeadEnd
|
|
197
194
|
end
|
198
195
|
|
199
196
|
private def after_lines
|
200
|
-
@code_lines[after_index.next
|
197
|
+
@code_lines[after_index.next..-1] || []
|
201
198
|
end
|
202
199
|
end
|
203
200
|
end
|
data/lib/dead_end/auto.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../dead_end
|
3
|
+
require_relative "../dead_end"
|
4
4
|
|
5
5
|
# Monkey patch kernel to ensure that all `require` calls call the same
|
6
6
|
# method
|
@@ -33,23 +33,3 @@ module Kernel
|
|
33
33
|
DeadEnd.handle_error(e)
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
37
|
-
# I honestly have no idea why this Object delegation is needed
|
38
|
-
# I keep staring at bootsnap and it doesn't have to do this
|
39
|
-
# is there a bug in their implementation they haven't caught or
|
40
|
-
# am I doing something different?
|
41
|
-
class Object
|
42
|
-
private
|
43
|
-
|
44
|
-
def load(path, wrap = false)
|
45
|
-
Kernel.load(path, wrap)
|
46
|
-
rescue SyntaxError => e
|
47
|
-
DeadEnd.handle_error(e)
|
48
|
-
end
|
49
|
-
|
50
|
-
def require(path)
|
51
|
-
Kernel.require(path)
|
52
|
-
rescue SyntaxError => e
|
53
|
-
DeadEnd.handle_error(e)
|
54
|
-
end
|
55
|
-
end
|
@@ -1,13 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DeadEnd
|
4
|
-
#
|
5
|
-
# code to give the user more context for the location of
|
6
|
-
# the problem.
|
4
|
+
# Turns a "invalid block(s)" into useful context
|
7
5
|
#
|
8
|
-
#
|
6
|
+
# There are three main phases in the algorithm:
|
9
7
|
#
|
10
|
-
#
|
8
|
+
# 1. Sanitize/format input source
|
9
|
+
# 2. Search for invalid blocks
|
10
|
+
# 3. Format invalid blocks into something meaninful
|
11
|
+
#
|
12
|
+
# This class handles the third part.
|
13
|
+
#
|
14
|
+
# The algorithm is very good at capturing all of a syntax
|
15
|
+
# error in a single block in number 2, however the results
|
16
|
+
# can contain ambiguities. Humans are good at pattern matching
|
17
|
+
# and filtering and can mentally remove extraneous data, but
|
18
|
+
# they can't add extra data that's not present.
|
19
|
+
#
|
20
|
+
# In the case of known ambiguious cases, this class adds context
|
21
|
+
# back to the ambiguitiy so the programmer has full information.
|
22
|
+
#
|
23
|
+
# Beyond handling these ambiguities, it also captures surrounding
|
24
|
+
# code context information:
|
11
25
|
#
|
12
26
|
# puts block.to_s # => "def bark"
|
13
27
|
#
|
@@ -16,7 +30,8 @@ module DeadEnd
|
|
16
30
|
# code_lines: code_lines
|
17
31
|
# )
|
18
32
|
#
|
19
|
-
#
|
33
|
+
# lines = context.call.map(&:original)
|
34
|
+
# puts lines.join
|
20
35
|
# # =>
|
21
36
|
# class Dog
|
22
37
|
# def bark
|
@@ -34,19 +49,34 @@ module DeadEnd
|
|
34
49
|
|
35
50
|
def call
|
36
51
|
@blocks.each do |block|
|
52
|
+
capture_first_kw_end_same_indent(block)
|
37
53
|
capture_last_end_same_indent(block)
|
38
54
|
capture_before_after_kws(block)
|
39
55
|
capture_falling_indent(block)
|
40
56
|
end
|
41
57
|
|
42
58
|
@lines_to_output.select!(&:not_empty?)
|
43
|
-
@lines_to_output.select!(&:not_comment?)
|
44
59
|
@lines_to_output.uniq!
|
45
60
|
@lines_to_output.sort!
|
46
61
|
|
47
62
|
@lines_to_output
|
48
63
|
end
|
49
64
|
|
65
|
+
# Shows the context around code provided by "falling" indentation
|
66
|
+
#
|
67
|
+
# Converts:
|
68
|
+
#
|
69
|
+
# it "foo" do
|
70
|
+
#
|
71
|
+
# into:
|
72
|
+
#
|
73
|
+
# class OH
|
74
|
+
# def hello
|
75
|
+
# it "foo" do
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
#
|
50
80
|
def capture_falling_indent(block)
|
51
81
|
AroundBlockScan.new(
|
52
82
|
block: block,
|
@@ -56,7 +86,36 @@ module DeadEnd
|
|
56
86
|
end
|
57
87
|
end
|
58
88
|
|
89
|
+
# Shows surrounding kw/end pairs
|
90
|
+
#
|
91
|
+
# The purpose of showing these extra pairs is due to cases
|
92
|
+
# of ambiguity when only one visible line is matched.
|
93
|
+
#
|
94
|
+
# For example:
|
95
|
+
#
|
96
|
+
# 1 class Dog
|
97
|
+
# 2 def bark
|
98
|
+
# 4 def eat
|
99
|
+
# 5 end
|
100
|
+
# 6 end
|
101
|
+
#
|
102
|
+
# In this case either line 2 could be missing an `end` or
|
103
|
+
# line 4 was an extra line added by mistake (it happens).
|
104
|
+
#
|
105
|
+
# When we detect the above problem it shows the issue
|
106
|
+
# as only being on line 2
|
107
|
+
#
|
108
|
+
# 2 def bark
|
109
|
+
#
|
110
|
+
# Showing "neighbor" keyword pairs gives extra context:
|
111
|
+
#
|
112
|
+
# 2 def bark
|
113
|
+
# 4 def eat
|
114
|
+
# 5 end
|
115
|
+
#
|
59
116
|
def capture_before_after_kws(block)
|
117
|
+
return unless block.visible_lines.count == 1
|
118
|
+
|
60
119
|
around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
|
61
120
|
.start_at_next_line
|
62
121
|
.capture_neighbor_context
|
@@ -66,9 +125,10 @@ module DeadEnd
|
|
66
125
|
@lines_to_output.concat(around_lines)
|
67
126
|
end
|
68
127
|
|
69
|
-
# When there is an invalid with a keyword
|
70
|
-
# right before
|
71
|
-
#
|
128
|
+
# When there is an invalid block with a keyword
|
129
|
+
# missing an end right before another end,
|
130
|
+
# it is unclear where which keyword is missing the
|
131
|
+
# end
|
72
132
|
#
|
73
133
|
# Take this example:
|
74
134
|
#
|
@@ -87,20 +147,21 @@ module DeadEnd
|
|
87
147
|
# line 4. Also work backwards and if there's a mis-matched keyword, show it
|
88
148
|
# too
|
89
149
|
def capture_last_end_same_indent(block)
|
90
|
-
|
91
|
-
|
150
|
+
return if block.visible_lines.length != 1
|
151
|
+
return unless block.visible_lines.first.is_kw?
|
152
|
+
|
153
|
+
visible_line = block.visible_lines.first
|
154
|
+
lines = @code_lines[visible_line.index..block.lines.last.index]
|
92
155
|
|
93
156
|
# Find first end with same indent
|
94
157
|
# (this would return line 4)
|
95
158
|
#
|
96
159
|
# end # 4
|
97
|
-
matching_end = lines.
|
160
|
+
matching_end = lines.detect { |line| line.indent == block.current_indent && line.is_end? }
|
98
161
|
return unless matching_end
|
99
162
|
|
100
163
|
@lines_to_output << matching_end
|
101
164
|
|
102
|
-
lines = @code_lines[start_index..matching_end.index]
|
103
|
-
|
104
165
|
# Work backwards from the end to
|
105
166
|
# see if there are mis-matched
|
106
167
|
# keyword/end pairs
|
@@ -113,7 +174,7 @@ module DeadEnd
|
|
113
174
|
# end # 4
|
114
175
|
end_count = 0
|
115
176
|
kw_count = 0
|
116
|
-
kw_line =
|
177
|
+
kw_line = @code_lines[visible_line.index..matching_end.index].reverse.detect do |line|
|
117
178
|
end_count += 1 if line.is_end?
|
118
179
|
kw_count += 1 if line.is_kw?
|
119
180
|
|
@@ -122,5 +183,51 @@ module DeadEnd
|
|
122
183
|
return unless kw_line
|
123
184
|
@lines_to_output << kw_line
|
124
185
|
end
|
186
|
+
|
187
|
+
# The logical inverse of `capture_last_end_same_indent`
|
188
|
+
#
|
189
|
+
# When there is an invalid block with an `end`
|
190
|
+
# missing a keyword right after another `end`,
|
191
|
+
# it is unclear where which end is missing the
|
192
|
+
# keyword.
|
193
|
+
#
|
194
|
+
# Take this example:
|
195
|
+
#
|
196
|
+
# class Dog # 1
|
197
|
+
# puts "woof" # 2
|
198
|
+
# end # 3
|
199
|
+
# end # 4
|
200
|
+
#
|
201
|
+
# the problem line will be identified as:
|
202
|
+
#
|
203
|
+
# ❯ end # 4
|
204
|
+
#
|
205
|
+
# This happens because lines 1, 2, and 3 are technically valid code and are expanded
|
206
|
+
# first, deemed valid, and hidden. We need to un-hide the matching keyword on
|
207
|
+
# line 1. Also work backwards and if there's a mis-matched end, show it
|
208
|
+
# too
|
209
|
+
def capture_first_kw_end_same_indent(block)
|
210
|
+
return if block.visible_lines.length != 1
|
211
|
+
return unless block.visible_lines.first.is_end?
|
212
|
+
|
213
|
+
visible_line = block.visible_lines.first
|
214
|
+
lines = @code_lines[block.lines.first.index..visible_line.index]
|
215
|
+
matching_kw = lines.reverse.detect { |line| line.indent == block.current_indent && line.is_kw? }
|
216
|
+
return unless matching_kw
|
217
|
+
|
218
|
+
@lines_to_output << matching_kw
|
219
|
+
|
220
|
+
kw_count = 0
|
221
|
+
end_count = 0
|
222
|
+
orphan_end = @code_lines[matching_kw.index..visible_line.index].detect do |line|
|
223
|
+
kw_count += 1 if line.is_kw?
|
224
|
+
end_count += 1 if line.is_end?
|
225
|
+
|
226
|
+
end_count >= kw_count
|
227
|
+
end
|
228
|
+
|
229
|
+
return unless orphan_end
|
230
|
+
@lines_to_output << orphan_end
|
231
|
+
end
|
125
232
|
end
|
126
233
|
end
|