dead_end 1.2.0 → 2.0.0
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/CHANGELOG.md +7 -0
- data/Gemfile.lock +1 -1
- data/lib/dead_end/around_block_scan.rb +5 -8
- data/lib/dead_end/capture_code_context.rb +123 -16
- data/lib/dead_end/clean_document.rb +313 -0
- data/lib/dead_end/code_frontier.rb +24 -13
- data/lib/dead_end/code_line.rb +159 -76
- data/lib/dead_end/code_search.rb +18 -32
- data/lib/dead_end/display_code_with_line_numbers.rb +0 -1
- data/lib/dead_end/display_invalid_blocks.rb +4 -2
- data/lib/dead_end/fyi.rb +2 -0
- data/lib/dead_end/internals.rb +9 -13
- data/lib/dead_end/lex_all.rb +10 -26
- 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/version.rb +1 -1
- data/lib/dead_end/who_dis_syntax_error.rb +1 -1
- metadata +4 -4
- data/lib/dead_end/heredoc_block_parse.rb +0 -34
- data/lib/dead_end/trailing_slash_join.rb +0 -53
data/lib/dead_end/code_line.rb
CHANGED
@@ -4,44 +4,47 @@ module DeadEnd
|
|
4
4
|
# Represents a single line of code of a given source file
|
5
5
|
#
|
6
6
|
# This object contains metadata about the line such as
|
7
|
-
# amount of indentation
|
7
|
+
# amount of indentation, if it is empty or not, and
|
8
|
+
# lexical data, such as if it has an `end` or a keyword
|
9
|
+
# in it.
|
8
10
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# as
|
12
|
-
#
|
13
|
-
# Visibility of lines can be toggled on and off.
|
11
|
+
# Visibility of lines can be toggled off. Marking a line as invisible
|
12
|
+
# indicates that it should not be used for syntax checks.
|
13
|
+
# It's functionally the same as commenting it out.
|
14
14
|
#
|
15
15
|
# Example:
|
16
16
|
#
|
17
|
-
# line = CodeLine.
|
18
|
-
# line.
|
17
|
+
# line = CodeLine.from_source("def foo\n").first
|
18
|
+
# line.number => 1
|
19
19
|
# line.empty? # => false
|
20
20
|
# line.visible? # => true
|
21
21
|
# line.mark_invisible
|
22
22
|
# line.visible? # => false
|
23
23
|
#
|
24
|
-
# A CodeBlock is made of multiple CodeLines
|
25
|
-
#
|
26
|
-
# Marking a line as invisible indicates that it should not be used
|
27
|
-
# for syntax checks. It's essentially the same as commenting it out
|
28
|
-
#
|
29
|
-
# Marking a line as invisible also lets the overall program know
|
30
|
-
# that it should not check that area for syntax errors.
|
31
24
|
class CodeLine
|
32
25
|
TRAILING_SLASH = ("\\" + $/).freeze
|
33
26
|
|
34
|
-
|
27
|
+
# Returns an array of CodeLine objects
|
28
|
+
# from the source string
|
29
|
+
def self.from_source(source)
|
30
|
+
lex_array_for_line = LexAll.new(source: source).each_with_object(Hash.new { |h, k| h[k] = [] }) { |lex, hash| hash[lex.line] << lex }
|
35
31
|
source.lines.map.with_index do |line, index|
|
36
|
-
CodeLine.new(
|
32
|
+
CodeLine.new(
|
33
|
+
line: line,
|
34
|
+
index: index,
|
35
|
+
lex: lex_array_for_line[index + 1]
|
36
|
+
)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
attr_reader :line, :index, :
|
40
|
+
attr_reader :line, :index, :lex, :line_number, :indent
|
41
|
+
def initialize(line:, index:, lex:)
|
42
|
+
@lex = lex
|
43
|
+
@line = line
|
44
|
+
@index = index
|
45
|
+
@original = line.freeze
|
46
|
+
@line_number = @index + 1
|
41
47
|
|
42
|
-
def initialize(line:, index:)
|
43
|
-
@original_line = line.freeze
|
44
|
-
@line = @original_line
|
45
48
|
if line.strip.empty?
|
46
49
|
@empty = true
|
47
50
|
@indent = 0
|
@@ -49,102 +52,182 @@ module DeadEnd
|
|
49
52
|
@empty = false
|
50
53
|
@indent = SpaceCount.indent(line)
|
51
54
|
end
|
52
|
-
@index = index
|
53
|
-
@status = nil # valid, invalid, unknown
|
54
|
-
@invalid = false
|
55
55
|
|
56
|
-
lex_detect!
|
57
|
-
end
|
58
|
-
|
59
|
-
private def lex_detect!
|
60
|
-
lex_array = LexAll.new(source: line)
|
61
56
|
kw_count = 0
|
62
57
|
end_count = 0
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
case lex.token
|
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 unless lex.expr_label?
|
71
|
-
when "def", "case", "for", "begin", "class", "module", "do"
|
72
|
-
kw_count += 1
|
73
|
-
when "end"
|
74
|
-
end_count += 1
|
75
|
-
end
|
58
|
+
@lex.each do |lex|
|
59
|
+
kw_count += 1 if lex.is_kw?
|
60
|
+
end_count += 1 if lex.is_end?
|
76
61
|
end
|
77
62
|
|
78
|
-
|
79
|
-
|
63
|
+
kw_count -= oneliner_method_count
|
64
|
+
|
80
65
|
@is_kw = (kw_count - end_count) > 0
|
81
66
|
@is_end = (end_count - kw_count) > 0
|
82
|
-
@is_trailing_slash = lex_array.last.token == TRAILING_SLASH
|
83
|
-
end
|
84
|
-
|
85
|
-
alias_method :original, :original_line
|
86
|
-
|
87
|
-
def trailing_slash?
|
88
|
-
@is_trailing_slash
|
89
67
|
end
|
90
68
|
|
69
|
+
# Used for stable sort via indentation level
|
70
|
+
#
|
71
|
+
# Ruby's sort is not "stable" meaning that when
|
72
|
+
# multiple elements have the same value, they are
|
73
|
+
# not guaranteed to return in the same order they
|
74
|
+
# were put in.
|
75
|
+
#
|
76
|
+
# So when multiple code lines have the same indentation
|
77
|
+
# level, they're sorted by their index value which is unique
|
78
|
+
# and consistent.
|
79
|
+
#
|
80
|
+
# This is mostly needed for consistency of the test suite
|
91
81
|
def indent_index
|
92
82
|
@indent_index ||= [indent, index]
|
93
83
|
end
|
84
|
+
alias_method :number, :line_number
|
94
85
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
def is_comment?
|
100
|
-
@is_comment
|
101
|
-
end
|
102
|
-
|
103
|
-
def not_comment?
|
104
|
-
!is_comment?
|
105
|
-
end
|
106
|
-
|
86
|
+
# Returns true if the code line is determined
|
87
|
+
# to contain a keyword that matches with an `end`
|
88
|
+
#
|
89
|
+
# For example: `def`, `do`, `begin`, `ensure`, etc.
|
107
90
|
def is_kw?
|
108
91
|
@is_kw
|
109
92
|
end
|
110
93
|
|
94
|
+
# Returns true if the code line is determined
|
95
|
+
# to contain an `end` keyword
|
111
96
|
def is_end?
|
112
97
|
@is_end
|
113
98
|
end
|
114
99
|
|
100
|
+
# Used to hide lines
|
101
|
+
#
|
102
|
+
# The search alorithm will group lines into blocks
|
103
|
+
# then if those blocks are determined to represent
|
104
|
+
# valid code they will be hidden
|
115
105
|
def mark_invisible
|
116
106
|
@line = ""
|
117
|
-
self
|
118
|
-
end
|
119
|
-
|
120
|
-
def mark_visible
|
121
|
-
@line = @original_line
|
122
|
-
self
|
123
107
|
end
|
124
108
|
|
109
|
+
# Means the line was marked as "invisible"
|
110
|
+
# Confusingly, "empty" lines are visible...they
|
111
|
+
# just don't contain any source code other than a newline ("\n").
|
125
112
|
def visible?
|
126
113
|
!line.empty?
|
127
114
|
end
|
128
115
|
|
116
|
+
# Opposite or `visible?` (note: different than `empty?`)
|
129
117
|
def hidden?
|
130
118
|
!visible?
|
131
119
|
end
|
132
120
|
|
133
|
-
|
134
|
-
|
121
|
+
# An `empty?` line is one that was originally left
|
122
|
+
# empty in the source code, while a "hidden" line
|
123
|
+
# is one that we've since marked as "invisible"
|
124
|
+
def empty?
|
125
|
+
@empty
|
135
126
|
end
|
136
|
-
alias_method :number, :line_number
|
137
127
|
|
128
|
+
# Opposite of `empty?` (note: different than `visible?`)
|
138
129
|
def not_empty?
|
139
130
|
!empty?
|
140
131
|
end
|
141
132
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
133
|
+
# Renders the given line
|
134
|
+
#
|
135
|
+
# Also allows us to represent source code as
|
136
|
+
# an array of code lines.
|
137
|
+
#
|
138
|
+
# When we have an array of code line elements
|
139
|
+
# calling `join` on the array will call `to_s`
|
140
|
+
# on each element, which essentially converts
|
141
|
+
# it back into it's original source string.
|
146
142
|
def to_s
|
147
143
|
line
|
148
144
|
end
|
145
|
+
|
146
|
+
# When the code line is marked invisible
|
147
|
+
# we retain the original value of it's line
|
148
|
+
# this is useful for debugging and for
|
149
|
+
# showing extra context
|
150
|
+
#
|
151
|
+
# DisplayCodeWithLineNumbers will render
|
152
|
+
# all lines given to it, not just visible
|
153
|
+
# lines, it uses the original method to
|
154
|
+
# obtain them.
|
155
|
+
attr_reader :original
|
156
|
+
|
157
|
+
# Comparison operator, needed for equality
|
158
|
+
# and sorting
|
159
|
+
def <=>(other)
|
160
|
+
index <=> other.index
|
161
|
+
end
|
162
|
+
|
163
|
+
# [Not stable API]
|
164
|
+
#
|
165
|
+
# Lines that have a `on_ignored_nl` type token and NOT
|
166
|
+
# a `BEG` type seem to be a good proxy for the ability
|
167
|
+
# to join multiple lines into one.
|
168
|
+
#
|
169
|
+
# This predicate method is used to determine when those
|
170
|
+
# two criteria have been met.
|
171
|
+
#
|
172
|
+
# The one known case this doesn't handle is:
|
173
|
+
#
|
174
|
+
# Ripper.lex <<~EOM
|
175
|
+
# a &&
|
176
|
+
# b ||
|
177
|
+
# c
|
178
|
+
# EOM
|
179
|
+
#
|
180
|
+
# For some reason this introduces `on_ignore_newline` but with BEG type
|
181
|
+
def ignore_newline_not_beg?
|
182
|
+
lex_value = lex.detect { |l| l.type == :on_ignored_nl }
|
183
|
+
!!(lex_value && !lex_value.expr_beg?)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Determines if the given line has a trailing slash
|
187
|
+
#
|
188
|
+
# lines = CodeLine.from_source(<<~EOM)
|
189
|
+
# it "foo" \
|
190
|
+
# EOM
|
191
|
+
# expect(lines.first.trailing_slash?).to eq(true)
|
192
|
+
#
|
193
|
+
def trailing_slash?
|
194
|
+
last = @lex.last
|
195
|
+
return false unless last
|
196
|
+
return false unless last.type == :on_sp
|
197
|
+
|
198
|
+
last.token == TRAILING_SLASH
|
199
|
+
end
|
200
|
+
|
201
|
+
# Endless method detection
|
202
|
+
#
|
203
|
+
# From https://github.com/ruby/irb/commit/826ae909c9c93a2ddca6f9cfcd9c94dbf53d44ab
|
204
|
+
# Detecting a "oneliner" seems to need a state machine.
|
205
|
+
# This can be done by looking mostly at the "state" (last value):
|
206
|
+
#
|
207
|
+
# ENDFN -> BEG (token = '=' ) -> END
|
208
|
+
#
|
209
|
+
private def oneliner_method_count
|
210
|
+
oneliner_count = 0
|
211
|
+
in_oneliner_def = nil
|
212
|
+
|
213
|
+
@lex.each do |lex|
|
214
|
+
if in_oneliner_def.nil?
|
215
|
+
in_oneliner_def = :ENDFN if lex.state.allbits?(Ripper::EXPR_ENDFN)
|
216
|
+
elsif lex.state.allbits?(Ripper::EXPR_ENDFN)
|
217
|
+
# Continue
|
218
|
+
elsif lex.state.allbits?(Ripper::EXPR_BEG)
|
219
|
+
in_oneliner_def = :BODY if lex.token == "="
|
220
|
+
elsif lex.state.allbits?(Ripper::EXPR_END)
|
221
|
+
# We found an endless method, count it
|
222
|
+
oneliner_count += 1 if in_oneliner_def == :BODY
|
223
|
+
|
224
|
+
in_oneliner_def = nil
|
225
|
+
else
|
226
|
+
in_oneliner_def = nil
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
oneliner_count
|
231
|
+
end
|
149
232
|
end
|
150
233
|
end
|
data/lib/dead_end/code_search.rb
CHANGED
@@ -3,11 +3,19 @@
|
|
3
3
|
module DeadEnd
|
4
4
|
# Searches code for a syntax error
|
5
5
|
#
|
6
|
+
# There are three main phases in the algorithm:
|
7
|
+
#
|
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 part.
|
13
|
+
#
|
6
14
|
# The bulk of the heavy lifting is done in:
|
7
15
|
#
|
8
16
|
# - CodeFrontier (Holds information for generating blocks and determining if we can stop searching)
|
9
17
|
# - ParseBlocksFromLine (Creates blocks into the frontier)
|
10
|
-
# - BlockExpand (Expands existing blocks to search more code
|
18
|
+
# - BlockExpand (Expands existing blocks to search more code)
|
11
19
|
#
|
12
20
|
# ## Syntax error detection
|
13
21
|
#
|
@@ -31,28 +39,24 @@ module DeadEnd
|
|
31
39
|
|
32
40
|
public
|
33
41
|
|
34
|
-
public
|
35
|
-
|
36
42
|
attr_reader :invalid_blocks, :record_dir, :code_lines
|
37
43
|
|
38
44
|
def initialize(source, record_dir: ENV["DEAD_END_RECORD_DIR"] || ENV["DEBUG"] ? "tmp" : nil)
|
39
|
-
@source = source
|
40
45
|
if record_dir
|
41
46
|
@time = Time.now.strftime("%Y-%m-%d-%H-%M-%s-%N")
|
42
47
|
@record_dir = Pathname(record_dir).join(@time).tap { |p| p.mkpath }
|
43
48
|
@write_count = 0
|
44
49
|
end
|
45
|
-
code_lines = source.lines.map.with_index do |line, i|
|
46
|
-
CodeLine.new(line: line, index: i)
|
47
|
-
end
|
48
50
|
|
49
|
-
@
|
51
|
+
@tick = 0
|
52
|
+
@source = source
|
53
|
+
@name_tick = Hash.new { |hash, k| hash[k] = 0 }
|
54
|
+
@invalid_blocks = []
|
55
|
+
|
56
|
+
@code_lines = CleanDocument.new(source: source).call.lines
|
50
57
|
|
51
58
|
@frontier = CodeFrontier.new(code_lines: @code_lines)
|
52
|
-
@
|
53
|
-
@name_tick = Hash.new { |hash, k| hash[k] = 0 }
|
54
|
-
@tick = 0
|
55
|
-
@block_expand = BlockExpand.new(code_lines: code_lines)
|
59
|
+
@block_expand = BlockExpand.new(code_lines: @code_lines)
|
56
60
|
@parse_blocks_from_indent_line = ParseBlocksFromIndentLine.new(code_lines: @code_lines)
|
57
61
|
end
|
58
62
|
|
@@ -63,10 +67,10 @@ module DeadEnd
|
|
63
67
|
filename = "#{@write_count += 1}-#{name}-#{@name_tick[name]}.txt"
|
64
68
|
if ENV["DEBUG"]
|
65
69
|
puts "\n\n==== #{filename} ===="
|
66
|
-
puts "\n```#{block.starts_at}
|
70
|
+
puts "\n```#{block.starts_at}..#{block.ends_at}"
|
67
71
|
puts block.to_s
|
68
72
|
puts "```"
|
69
|
-
puts " block indent:
|
73
|
+
puts " block indent: #{block.current_indent}"
|
70
74
|
end
|
71
75
|
@record_dir.join(filename).open(mode: "a") do |f|
|
72
76
|
display = DisplayInvalidBlocks.new(
|
@@ -122,26 +126,8 @@ module DeadEnd
|
|
122
126
|
push(block, name: "expand")
|
123
127
|
end
|
124
128
|
|
125
|
-
def sweep_heredocs
|
126
|
-
HeredocBlockParse.new(
|
127
|
-
source: @source,
|
128
|
-
code_lines: @code_lines
|
129
|
-
).call.each do |block|
|
130
|
-
push(block, name: "heredoc")
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def sweep_comments
|
135
|
-
lines = @code_lines.select(&:is_comment?)
|
136
|
-
return if lines.empty?
|
137
|
-
block = CodeBlock.new(lines: lines)
|
138
|
-
sweep(block: block, name: "comments")
|
139
|
-
end
|
140
|
-
|
141
129
|
# Main search loop
|
142
130
|
def call
|
143
|
-
sweep_heredocs
|
144
|
-
sweep_comments
|
145
131
|
until frontier.holds_all_syntax_errors?
|
146
132
|
@tick += 1
|
147
133
|
|
data/lib/dead_end/fyi.rb
CHANGED
data/lib/dead_end/internals.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
3
|
# This is the top level file, but is moved to `internals`
|
5
|
-
# so the top level
|
4
|
+
# so the top level require can instead enable the "automatic" behavior
|
6
5
|
|
7
6
|
require_relative "version"
|
8
7
|
|
@@ -15,7 +14,7 @@ require "timeout"
|
|
15
14
|
module DeadEnd
|
16
15
|
class Error < StandardError; end
|
17
16
|
SEARCH_SOURCE_ON_ERROR_DEFAULT = true
|
18
|
-
TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT",
|
17
|
+
TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 1).to_i
|
19
18
|
|
20
19
|
def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT)
|
21
20
|
raise e unless e.message.include?("end-of-input")
|
@@ -33,8 +32,6 @@ module DeadEnd
|
|
33
32
|
)
|
34
33
|
end
|
35
34
|
|
36
|
-
warn ""
|
37
|
-
warn ""
|
38
35
|
raise e
|
39
36
|
end
|
40
37
|
|
@@ -145,14 +142,13 @@ end
|
|
145
142
|
|
146
143
|
require_relative "code_line"
|
147
144
|
require_relative "code_block"
|
145
|
+
require_relative "code_search"
|
148
146
|
require_relative "code_frontier"
|
149
|
-
require_relative "
|
150
|
-
require_relative "around_block_scan"
|
151
|
-
require_relative "block_expand"
|
152
|
-
require_relative "parse_blocks_from_indent_line"
|
147
|
+
require_relative "clean_document"
|
153
148
|
|
154
|
-
require_relative "code_search"
|
155
|
-
require_relative "who_dis_syntax_error"
|
156
|
-
require_relative "heredoc_block_parse"
|
157
149
|
require_relative "lex_all"
|
158
|
-
require_relative "
|
150
|
+
require_relative "block_expand"
|
151
|
+
require_relative "around_block_scan"
|
152
|
+
require_relative "who_dis_syntax_error"
|
153
|
+
require_relative "display_invalid_blocks"
|
154
|
+
require_relative "parse_blocks_from_indent_line"
|
data/lib/dead_end/lex_all.rb
CHANGED
@@ -24,6 +24,10 @@ module DeadEnd
|
|
24
24
|
@lex.map! { |(line, _), type, token, state| LexValue.new(line, type, token, state) }
|
25
25
|
end
|
26
26
|
|
27
|
+
def to_a
|
28
|
+
@lex
|
29
|
+
end
|
30
|
+
|
27
31
|
def each
|
28
32
|
return @lex.each unless block_given?
|
29
33
|
@lex.each do |x|
|
@@ -31,34 +35,14 @@ module DeadEnd
|
|
31
35
|
end
|
32
36
|
end
|
33
37
|
|
34
|
-
def
|
35
|
-
@lex
|
38
|
+
def [](index)
|
39
|
+
@lex[index]
|
36
40
|
end
|
37
41
|
|
38
|
-
|
39
|
-
|
40
|
-
# This lex:
|
41
|
-
#
|
42
|
-
# [1, 0], :on_ident, "describe", CMDARG
|
43
|
-
#
|
44
|
-
# Would translate into:
|
45
|
-
#
|
46
|
-
# lex.line # => 1
|
47
|
-
# lex.type # => :on_indent
|
48
|
-
# lex.token # => "describe"
|
49
|
-
class LexValue
|
50
|
-
attr_reader :line, :type, :token, :state
|
51
|
-
|
52
|
-
def initialize(line, type, token, state)
|
53
|
-
@line = line
|
54
|
-
@type = type
|
55
|
-
@token = token
|
56
|
-
@state = state
|
57
|
-
end
|
58
|
-
|
59
|
-
def expr_label?
|
60
|
-
state.allbits?(Ripper::EXPR_LABEL)
|
61
|
-
end
|
42
|
+
def last
|
43
|
+
@lex.last
|
62
44
|
end
|
63
45
|
end
|
64
46
|
end
|
47
|
+
|
48
|
+
require_relative "lex_value"
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module DeadEnd
|
2
|
+
# Value object for accessing lex values
|
3
|
+
#
|
4
|
+
# This lex:
|
5
|
+
#
|
6
|
+
# [1, 0], :on_ident, "describe", CMDARG
|
7
|
+
#
|
8
|
+
# Would translate into:
|
9
|
+
#
|
10
|
+
# lex.line # => 1
|
11
|
+
# lex.type # => :on_indent
|
12
|
+
# lex.token # => "describe"
|
13
|
+
class LexValue
|
14
|
+
attr_reader :line, :type, :token, :state
|
15
|
+
|
16
|
+
def initialize(line, type, token, state)
|
17
|
+
@line = line
|
18
|
+
@type = type
|
19
|
+
@token = token
|
20
|
+
@state = state
|
21
|
+
|
22
|
+
set_kw_end
|
23
|
+
end
|
24
|
+
|
25
|
+
private def set_kw_end
|
26
|
+
@is_end = false
|
27
|
+
@is_kw = false
|
28
|
+
return if type != :on_kw
|
29
|
+
|
30
|
+
case token
|
31
|
+
when "if", "unless", "while", "until"
|
32
|
+
# Only count if/unless when it's not a "trailing" if/unless
|
33
|
+
# https://github.com/ruby/ruby/blob/06b44f819eb7b5ede1ff69cecb25682b56a1d60c/lib/irb/ruby-lex.rb#L374-L375
|
34
|
+
@is_kw = true unless expr_label?
|
35
|
+
when "def", "case", "for", "begin", "class", "module", "do"
|
36
|
+
@is_kw = true
|
37
|
+
when "end"
|
38
|
+
@is_end = true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ignore_newline?
|
43
|
+
type == :on_ignored_nl
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_end?
|
47
|
+
@is_end
|
48
|
+
end
|
49
|
+
|
50
|
+
def is_kw?
|
51
|
+
@is_kw
|
52
|
+
end
|
53
|
+
|
54
|
+
def expr_beg?
|
55
|
+
state.anybits?(Ripper::EXPR_BEG)
|
56
|
+
end
|
57
|
+
|
58
|
+
def expr_label?
|
59
|
+
state.allbits?(Ripper::EXPR_LABEL)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -4,7 +4,7 @@ module DeadEnd
|
|
4
4
|
# This class is responsible for generating initial code blocks
|
5
5
|
# that will then later be expanded.
|
6
6
|
#
|
7
|
-
# The biggest concern when guessing
|
7
|
+
# The biggest concern when guessing code blocks, is accidentally
|
8
8
|
# grabbing one that contains only an "end". In this example:
|
9
9
|
#
|
10
10
|
# def dog
|
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:
|
4
|
+
version: 2.0.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-10-
|
11
|
+
date: 2021-10-11 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
|
@@ -40,6 +40,7 @@ files:
|
|
40
40
|
- lib/dead_end/auto.rb
|
41
41
|
- lib/dead_end/block_expand.rb
|
42
42
|
- lib/dead_end/capture_code_context.rb
|
43
|
+
- lib/dead_end/clean_document.rb
|
43
44
|
- lib/dead_end/code_block.rb
|
44
45
|
- lib/dead_end/code_frontier.rb
|
45
46
|
- lib/dead_end/code_line.rb
|
@@ -47,11 +48,10 @@ files:
|
|
47
48
|
- lib/dead_end/display_code_with_line_numbers.rb
|
48
49
|
- lib/dead_end/display_invalid_blocks.rb
|
49
50
|
- lib/dead_end/fyi.rb
|
50
|
-
- lib/dead_end/heredoc_block_parse.rb
|
51
51
|
- lib/dead_end/internals.rb
|
52
52
|
- lib/dead_end/lex_all.rb
|
53
|
+
- lib/dead_end/lex_value.rb
|
53
54
|
- lib/dead_end/parse_blocks_from_indent_line.rb
|
54
|
-
- lib/dead_end/trailing_slash_join.rb
|
55
55
|
- lib/dead_end/version.rb
|
56
56
|
- lib/dead_end/who_dis_syntax_error.rb
|
57
57
|
homepage: https://github.com/zombocom/dead_end.git
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module DeadEnd
|
4
|
-
# Takes in a source, and returns blocks containing each heredoc
|
5
|
-
class HeredocBlockParse
|
6
|
-
private
|
7
|
-
|
8
|
-
attr_reader :code_lines, :lex
|
9
|
-
|
10
|
-
public
|
11
|
-
|
12
|
-
def initialize(source:, code_lines:)
|
13
|
-
@code_lines = code_lines
|
14
|
-
@lex = LexAll.new(source: source)
|
15
|
-
end
|
16
|
-
|
17
|
-
def call
|
18
|
-
blocks = []
|
19
|
-
beginning = []
|
20
|
-
@lex.each do |lex|
|
21
|
-
case lex.type
|
22
|
-
when :on_heredoc_beg
|
23
|
-
beginning << lex.line
|
24
|
-
when :on_heredoc_end
|
25
|
-
start_index = beginning.pop - 1
|
26
|
-
end_index = lex.line - 1
|
27
|
-
blocks << CodeBlock.new(lines: code_lines[start_index..end_index])
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
blocks
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|