dead_end 1.2.0 → 3.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/.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
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeadEnd
|
4
|
+
# Find mis-matched syntax based on lexical count
|
5
|
+
#
|
6
|
+
# Used for detecting missing pairs of elements
|
7
|
+
# each keyword needs an end, each '{' needs a '}'
|
8
|
+
# etc.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# left_right = LeftRightLexCount.new
|
13
|
+
# left_right.count_kw
|
14
|
+
# left_right.missing.first
|
15
|
+
# # => "end"
|
16
|
+
#
|
17
|
+
# left_right = LeftRightLexCount.new
|
18
|
+
# source = "{ a: b, c: d" # Note missing '}'
|
19
|
+
# LexAll.new(source: source).each do |lex|
|
20
|
+
# left_right.count_lex(lex)
|
21
|
+
# end
|
22
|
+
# left_right.missing.first
|
23
|
+
# # => "}"
|
24
|
+
class LeftRightLexCount
|
25
|
+
def initialize
|
26
|
+
@kw_count = 0
|
27
|
+
@end_count = 0
|
28
|
+
|
29
|
+
@count_for_char = {
|
30
|
+
"{" => 0,
|
31
|
+
"}" => 0,
|
32
|
+
"[" => 0,
|
33
|
+
"]" => 0,
|
34
|
+
"(" => 0,
|
35
|
+
")" => 0,
|
36
|
+
"|" => 0
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def count_kw
|
41
|
+
@kw_count += 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def count_end
|
45
|
+
@end_count += 1
|
46
|
+
end
|
47
|
+
|
48
|
+
# Count source code characters
|
49
|
+
#
|
50
|
+
# Example:
|
51
|
+
#
|
52
|
+
# left_right = LeftRightLexCount.new
|
53
|
+
# left_right.count_lex(LexValue.new(1, :on_lbrace, "{", Ripper::EXPR_BEG))
|
54
|
+
# left_right.count_for_char("{")
|
55
|
+
# # => 1
|
56
|
+
# left_right.count_for_char("}")
|
57
|
+
# # => 0
|
58
|
+
def count_lex(lex)
|
59
|
+
case lex.type
|
60
|
+
when :on_tstring_content
|
61
|
+
# ^^^
|
62
|
+
# Means it's a string or a symbol `"{"` rather than being
|
63
|
+
# part of a data structure (like a hash) `{ a: b }`
|
64
|
+
when :on_embexpr_beg
|
65
|
+
# ^^^
|
66
|
+
# Embedded string expressions like `"#{foo} <-embed"`
|
67
|
+
# are parsed with chars:
|
68
|
+
#
|
69
|
+
# `#{` as :on_embexpr_beg
|
70
|
+
# `}` as :on_embexpr_end
|
71
|
+
#
|
72
|
+
# We cannot ignore both :on_emb_expr_beg and :on_embexpr_end
|
73
|
+
# because sometimes the lexer thinks something is an embed
|
74
|
+
# string end, when it is not like `lol = }` (no clue why).
|
75
|
+
#
|
76
|
+
# When we see `#{` count it as a `{` or we will
|
77
|
+
# have a mis-match count.
|
78
|
+
#
|
79
|
+
case lex.token
|
80
|
+
when "\#{"
|
81
|
+
@count_for_char["{"] += 1
|
82
|
+
end
|
83
|
+
else
|
84
|
+
@end_count += 1 if lex.is_end?
|
85
|
+
@kw_count += 1 if lex.is_kw?
|
86
|
+
@count_for_char[lex.token] += 1 if @count_for_char.key?(lex.token)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def count_for_char(char)
|
91
|
+
@count_for_char[char]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns an array of missing syntax characters
|
95
|
+
# or `"end"` or `"keyword"`
|
96
|
+
#
|
97
|
+
# left_right.missing
|
98
|
+
# # => ["}"]
|
99
|
+
def missing
|
100
|
+
out = missing_pairs
|
101
|
+
out << missing_pipe
|
102
|
+
out << missing_keyword_end
|
103
|
+
out.compact!
|
104
|
+
out
|
105
|
+
end
|
106
|
+
|
107
|
+
PAIRS = {
|
108
|
+
"{" => "}",
|
109
|
+
"[" => "]",
|
110
|
+
"(" => ")"
|
111
|
+
}.freeze
|
112
|
+
|
113
|
+
# Opening characters like `{` need closing characters # like `}`.
|
114
|
+
#
|
115
|
+
# When a mis-match count is detected, suggest the
|
116
|
+
# missing member.
|
117
|
+
#
|
118
|
+
# For example if there are 3 `}` and only two `{`
|
119
|
+
# return `"{"`
|
120
|
+
private def missing_pairs
|
121
|
+
PAIRS.map do |(left, right)|
|
122
|
+
case @count_for_char[left] <=> @count_for_char[right]
|
123
|
+
when 1
|
124
|
+
right
|
125
|
+
when 0
|
126
|
+
nil
|
127
|
+
when -1
|
128
|
+
left
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Keywords need ends and ends need keywords
|
134
|
+
#
|
135
|
+
# If we have more keywords, there's a missing `end`
|
136
|
+
# if we have more `end`-s, there's a missing keyword
|
137
|
+
private def missing_keyword_end
|
138
|
+
case @kw_count <=> @end_count
|
139
|
+
when 1
|
140
|
+
"end"
|
141
|
+
when 0
|
142
|
+
nil
|
143
|
+
when -1
|
144
|
+
"keyword"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Pipes come in pairs.
|
149
|
+
# If there's an odd number of pipes then we
|
150
|
+
# are missing one
|
151
|
+
private def missing_pipe
|
152
|
+
if @count_for_char["|"].odd?
|
153
|
+
"|"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/lib/dead_end/lex_all.rb
CHANGED
@@ -15,7 +15,7 @@ module DeadEnd
|
|
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..-1]
|
19
19
|
|
20
20
|
@lex.concat(Ripper.lex(lines.join, "-", lineno + 1))
|
21
21
|
lineno = @lex.last.first.first + 1
|
@@ -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
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeadEnd
|
4
|
+
# Capture parse errors from ripper
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# puts RipperErrors.new(" def foo").call.errors
|
9
|
+
# # => ["syntax error, unexpected end-of-input, expecting ';' or '\\n'"]
|
10
|
+
class RipperErrors < Ripper
|
11
|
+
attr_reader :errors
|
12
|
+
|
13
|
+
# Comes from ripper, called
|
14
|
+
# on every parse error, msg
|
15
|
+
# is a string
|
16
|
+
def on_parse_error(msg)
|
17
|
+
@errors ||= []
|
18
|
+
@errors << msg
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
@run_once ||= begin
|
23
|
+
@errors = []
|
24
|
+
parse
|
25
|
+
true
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/dead_end/version.rb
CHANGED
data/lib/dead_end.rb
CHANGED
@@ -1,4 +1,148 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "dead_end/
|
3
|
+
require_relative "dead_end/version"
|
4
|
+
|
5
|
+
require "tmpdir"
|
6
|
+
require "stringio"
|
7
|
+
require "pathname"
|
8
|
+
require "ripper"
|
9
|
+
require "timeout"
|
10
|
+
|
11
|
+
module DeadEnd
|
12
|
+
# Used to indicate a default value that cannot
|
13
|
+
# be confused with another input
|
14
|
+
DEFAULT_VALUE = Object.new.freeze
|
15
|
+
|
16
|
+
class Error < StandardError; end
|
17
|
+
TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 1).to_i
|
18
|
+
|
19
|
+
def self.handle_error(e)
|
20
|
+
filename = e.message.split(":").first
|
21
|
+
$stderr.sync = true
|
22
|
+
|
23
|
+
call(
|
24
|
+
source: Pathname(filename).read,
|
25
|
+
filename: filename
|
26
|
+
)
|
27
|
+
|
28
|
+
raise e
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
|
32
|
+
search = nil
|
33
|
+
filename = nil if filename == DEFAULT_VALUE
|
34
|
+
Timeout.timeout(timeout) do
|
35
|
+
record_dir ||= ENV["DEBUG"] ? "tmp" : nil
|
36
|
+
search = CodeSearch.new(source, record_dir: record_dir).call
|
37
|
+
end
|
38
|
+
|
39
|
+
blocks = search.invalid_blocks
|
40
|
+
DisplayInvalidBlocks.new(
|
41
|
+
io: io,
|
42
|
+
blocks: blocks,
|
43
|
+
filename: filename,
|
44
|
+
terminal: terminal,
|
45
|
+
code_lines: search.code_lines
|
46
|
+
).call
|
47
|
+
rescue Timeout::Error => e
|
48
|
+
io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
|
49
|
+
io.puts e.backtrace.first(3).join($/)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Used for counting spaces
|
53
|
+
module SpaceCount
|
54
|
+
def self.indent(string)
|
55
|
+
string.split(/\S/).first&.length || 0
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# This will tell you if the `code_lines` would be valid
|
60
|
+
# if you removed the `without_lines`. In short it's a
|
61
|
+
# way to detect if we've found the lines with syntax errors
|
62
|
+
# in our document yet.
|
63
|
+
#
|
64
|
+
# code_lines = [
|
65
|
+
# CodeLine.new(line: "def foo\n", index: 0)
|
66
|
+
# CodeLine.new(line: " def bar\n", index: 1)
|
67
|
+
# CodeLine.new(line: "end\n", index: 2)
|
68
|
+
# ]
|
69
|
+
#
|
70
|
+
# DeadEnd.valid_without?(
|
71
|
+
# without_lines: code_lines[1],
|
72
|
+
# code_lines: code_lines
|
73
|
+
# ) # => true
|
74
|
+
#
|
75
|
+
# DeadEnd.valid?(code_lines) # => false
|
76
|
+
def self.valid_without?(without_lines:, code_lines:)
|
77
|
+
lines = code_lines - Array(without_lines).flatten
|
78
|
+
|
79
|
+
if lines.empty?
|
80
|
+
true
|
81
|
+
else
|
82
|
+
valid?(lines)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.invalid?(source)
|
87
|
+
source = source.join if source.is_a?(Array)
|
88
|
+
source = source.to_s
|
89
|
+
|
90
|
+
Ripper.new(source).tap(&:parse).error?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns truthy if a given input source is valid syntax
|
94
|
+
#
|
95
|
+
# DeadEnd.valid?(<<~EOM) # => true
|
96
|
+
# def foo
|
97
|
+
# end
|
98
|
+
# EOM
|
99
|
+
#
|
100
|
+
# DeadEnd.valid?(<<~EOM) # => false
|
101
|
+
# def foo
|
102
|
+
# def bar # Syntax error here
|
103
|
+
# end
|
104
|
+
# EOM
|
105
|
+
#
|
106
|
+
# You can also pass in an array of lines and they'll be
|
107
|
+
# joined before evaluating
|
108
|
+
#
|
109
|
+
# DeadEnd.valid?(
|
110
|
+
# [
|
111
|
+
# "def foo\n",
|
112
|
+
# "end\n"
|
113
|
+
# ]
|
114
|
+
# ) # => true
|
115
|
+
#
|
116
|
+
# DeadEnd.valid?(
|
117
|
+
# [
|
118
|
+
# "def foo\n",
|
119
|
+
# " def bar\n", # Syntax error here
|
120
|
+
# "end\n"
|
121
|
+
# ]
|
122
|
+
# ) # => false
|
123
|
+
#
|
124
|
+
# As an FYI the CodeLine class instances respond to `to_s`
|
125
|
+
# so passing a CodeLine in as an object or as an array
|
126
|
+
# will convert it to it's code representation.
|
127
|
+
def self.valid?(source)
|
128
|
+
!invalid?(source)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
require_relative "dead_end/code_line"
|
133
|
+
require_relative "dead_end/code_block"
|
134
|
+
require_relative "dead_end/code_search"
|
135
|
+
require_relative "dead_end/code_frontier"
|
136
|
+
require_relative "dead_end/clean_document"
|
137
|
+
|
138
|
+
require_relative "dead_end/lex_all"
|
139
|
+
require_relative "dead_end/block_expand"
|
140
|
+
require_relative "dead_end/around_block_scan"
|
141
|
+
require_relative "dead_end/ripper_errors"
|
142
|
+
require_relative "dead_end/display_invalid_blocks"
|
143
|
+
require_relative "dead_end/parse_blocks_from_indent_line"
|
144
|
+
|
145
|
+
require_relative "dead_end/explain_syntax"
|
146
|
+
|
4
147
|
require_relative "dead_end/auto"
|
148
|
+
require_relative "dead_end/cli"
|
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: 3.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-
|
11
|
+
date: 2021-11-03 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,20 +40,21 @@ 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
|
44
|
+
- lib/dead_end/cli.rb
|
43
45
|
- lib/dead_end/code_block.rb
|
44
46
|
- lib/dead_end/code_frontier.rb
|
45
47
|
- lib/dead_end/code_line.rb
|
46
48
|
- lib/dead_end/code_search.rb
|
47
49
|
- lib/dead_end/display_code_with_line_numbers.rb
|
48
50
|
- lib/dead_end/display_invalid_blocks.rb
|
49
|
-
- lib/dead_end/
|
50
|
-
- lib/dead_end/
|
51
|
-
- lib/dead_end/internals.rb
|
51
|
+
- lib/dead_end/explain_syntax.rb
|
52
|
+
- lib/dead_end/left_right_lex_count.rb
|
52
53
|
- lib/dead_end/lex_all.rb
|
54
|
+
- lib/dead_end/lex_value.rb
|
53
55
|
- lib/dead_end/parse_blocks_from_indent_line.rb
|
54
|
-
- lib/dead_end/
|
56
|
+
- lib/dead_end/ripper_errors.rb
|
55
57
|
- lib/dead_end/version.rb
|
56
|
-
- lib/dead_end/who_dis_syntax_error.rb
|
57
58
|
homepage: https://github.com/zombocom/dead_end.git
|
58
59
|
licenses:
|
59
60
|
- MIT
|
data/lib/dead_end/fyi.rb
DELETED
@@ -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
|