Skip to content

Commit 8f6083f

Browse files
committed
[Close #21] Concat lines with trailing slash
Ruby lines that end with a slash `\` indicate that the next line should be concatenated with the current line so: ```ruby if "trailing" \ "line" do end ``` Is functionally equivalent to: ```ruby if "trailing" "line" do endf ``` We can replicate this logic by taking note when a line ends with a trailing slash and then concatenating it with the next line. Functionally the TrailingSlashJoin class does this by concatenating the lines, and then hiding the original line. To allow for output, the DisplayCodeWithLineNumbers is now aware that a code line may contain multiple code lines, and it will split and render each.
1 parent 0b915d3 commit 8f6083f

File tree

8 files changed

+246
-31
lines changed

8 files changed

+246
-31
lines changed

lib/syntax_search.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def self.call(source: , filename: , terminal: false, record_dir: nil, timeout: T
5050
io: $stderr
5151
).call
5252
rescue Timeout::Error
53-
$stderr.puts "Syntax search timed out SYNTAX_SEARCH_TIMEOUT=#{timeout}"
53+
$stderr.puts "Syntax search timed out SYNTAX_SEARCH_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
5454
end
5555

5656
# Used for counting spaces
@@ -150,3 +150,4 @@ def self.invalid_type(source)
150150
require_relative "syntax_search/who_dis_syntax_error"
151151
require_relative "syntax_search/heredoc_block_parse"
152152
require_relative "syntax_search/lex_all"
153+
require_relative "syntax_search/trailing_slash_join"

lib/syntax_search/code_line.rb

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ module SyntaxErrorSearch
2929
# Marking a line as invisible also lets the overall program know
3030
# that it should not check that area for syntax errors.
3131
class CodeLine
32+
TRAILING_SLASH = ("\\" + $/).freeze
33+
3234
attr_reader :line, :index, :indent, :original_line
3335

3436
def initialize(line: , index:)
@@ -40,24 +42,34 @@ def initialize(line: , index:)
4042
@status = nil # valid, invalid, unknown
4143
@invalid = false
4244

43-
@kw_count = 0
44-
@end_count = 0
45-
@lex = LexAll.new(source: line)
46-
@lex.each do |lex|
45+
lex_detect!
46+
end
47+
48+
private def lex_detect!
49+
lex = LexAll.new(source: line)
50+
kw_count = 0
51+
end_count = 0
52+
lex.each do |lex|
4753
next unless lex.type == :on_kw
4854

4955
case lex.token
5056
when 'def', 'case', 'for', 'begin', 'class', 'module', 'if', 'unless', 'while', 'until' , 'do'
51-
@kw_count += 1
57+
kw_count += 1
5258
when 'end'
53-
@end_count += 1
59+
end_count += 1
5460
end
5561
end
5662

57-
@is_comment = true if @lex.detect {|lex| lex.type != :on_sp}&.type == :on_comment
63+
@is_kw = (kw_count - end_count) > 0
64+
@is_end = (end_count - kw_count) > 0
65+
@is_comment = lex.detect {|lex| lex.type != :on_sp}&.type == :on_comment
66+
@is_trailing_slash = lex.last.token == TRAILING_SLASH
67+
end
68+
69+
alias :original :original_line
5870

59-
@is_kw = (@kw_count - @end_count) > 0
60-
@is_end = (@end_count - @kw_count) > 0
71+
def trailing_slash?
72+
@is_trailing_slash
6173
end
6274

6375
def <=>(b)
@@ -110,7 +122,6 @@ def hidden?
110122
def line_number
111123
index + 1
112124
end
113-
114125
alias :number :line_number
115126

116127
def not_empty?

lib/syntax_search/code_search.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ def initialize(source, record_dir: ENV["SYNTAX_SEARCH_RECORD_DIR"] || ENV["DEBUG
3535
@record_dir = Pathname(record_dir).join(@time).tap {|p| p.mkpath }
3636
@write_count = 0
3737
end
38-
@code_lines = source.lines.map.with_index do |line, i|
38+
code_lines = source.lines.map.with_index do |line, i|
3939
CodeLine.new(line: line, index: i)
4040
end
41+
42+
@code_lines = TrailingSlashJoin.new(code_lines: code_lines).call
43+
4144
@frontier = CodeFrontier.new(code_lines: @code_lines)
4245
@invalid_blocks = []
4346
@name_tick = Hash.new {|hash, k| hash[k] = 0 }

lib/syntax_search/display_code_with_line_numbers.rb

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,33 +24,48 @@ class DisplayCodeWithLineNumbers
2424
TERMINAL_END = "\e[0m"
2525

2626
def initialize(lines: , highlight_lines: [], terminal: false)
27-
@lines = lines.sort
27+
@lines = Array(lines).sort
2828
@terminal = terminal
29-
@highlight_line_hash = 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 }
3030
@digit_count = @lines.last&.line_number.to_s.length
3131
end
3232

3333
def call
3434
@lines.map do |line|
35-
string = String.new("")
36-
if @highlight_line_hash[line]
37-
string << "❯ "
38-
else
39-
string << " "
40-
end
35+
format_line(line)
36+
end.join
37+
end
4138

42-
number = line.line_number.to_s.rjust(@digit_count)
43-
string << number.to_s
44-
if line.empty?
45-
string << line.original_line
46-
else
47-
string << " "
48-
string << TERMINAL_HIGHLIGHT if @terminal && @highlight_line_hash[line] # Bold, italics
49-
string << line.original_line
50-
string << TERMINAL_END if @terminal
51-
end
52-
string
39+
private def format_line(code_line)
40+
# Handle trailing slash lines
41+
code_line.original.lines.map.with_index do |contents, i|
42+
format(
43+
empty: code_line.empty?,
44+
number: (code_line.number + i).to_s,
45+
contents: contents,
46+
highlight: @highlight_line_hash[code_line]
47+
)
5348
end.join
5449
end
50+
51+
private def format(contents: , number: , highlight: false, empty:)
52+
string = String.new("")
53+
if highlight
54+
string << "❯ "
55+
else
56+
string << " "
57+
end
58+
59+
string << number.rjust(@digit_count).to_s
60+
if empty
61+
string << contents
62+
else
63+
string << " "
64+
string << TERMINAL_HIGHLIGHT if @terminal && highlight
65+
string << contents
66+
string << TERMINAL_END if @terminal
67+
end
68+
string
69+
end
5570
end
5671
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# frozen_string_literal: true
2+
3+
module SyntaxErrorSearch
4+
# Handles code that contains trailing slashes
5+
# by turning multiple lines with trailing slash(es) into
6+
# a single code line
7+
#
8+
# expect(code_lines.join).to eq(<<~EOM)
9+
# it "trailing \
10+
# "slash" do
11+
# end
12+
# EOM
13+
#
14+
# lines = TrailngSlashJoin(code_lines: code_lines).call
15+
# expect(lines.first.to_s).to eq(<<~EOM)
16+
# it "trailing \
17+
# "slash" do
18+
# EOM
19+
#
20+
class TrailingSlashJoin
21+
def initialize(code_lines:)
22+
@code_lines = code_lines
23+
@code_lines_dup = code_lines.dup
24+
end
25+
26+
def call
27+
@trailing_lines = []
28+
@code_lines.select(&:trailing_slash?).each do |trailing|
29+
stop_next = false
30+
lines = @code_lines[trailing.index..-1].take_while do |line|
31+
next false if stop_next
32+
33+
if !line.trailing_slash?
34+
stop_next = true
35+
end
36+
37+
true
38+
end
39+
40+
joined_line = CodeLine.new(line: lines.map(&:original_line).join, index: trailing.index)
41+
42+
@code_lines_dup[trailing.index] = joined_line
43+
44+
@trailing_lines << joined_line
45+
46+
lines.shift # Don't hide first trailing slash line
47+
lines.each(&:mark_invisible)
48+
end
49+
50+
return @code_lines_dup
51+
end
52+
end
53+
end

spec/unit/code_line_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44

55
module SyntaxErrorSearch
66
RSpec.describe CodeLine do
7+
it "trailing slash" do
8+
code_lines = code_line_array(<<~'EOM')
9+
it "trailing s" \
10+
"lash" do
11+
EOM
12+
13+
expect(code_lines.map(&:trailing_slash?)).to eq([true, false])
14+
15+
code_lines = code_line_array(<<~'EOM')
16+
amazing_print: ->(obj) { obj.ai + "\n" },
17+
EOM
18+
expect(code_lines.map(&:trailing_slash?)).to eq([false])
19+
end
20+
721
it "knows it's a comment" do
822
line = CodeLine.new(line: " # iama comment", index: 0)
923
expect(line.is_comment?).to be_truthy

spec/unit/code_search_spec.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@
44

55
module SyntaxErrorSearch
66
RSpec.describe CodeSearch do
7+
it "handles no spaces between blocks" do
8+
search = CodeSearch.new(<<~'EOM')
9+
require "rails_helper"
10+
RSpec.describe TelehealthAppointment, type: :model do
11+
describe "#participants_state" do
12+
context "timezones workaround" do
13+
it "should receive a time in UTC format and return the time with the"\
14+
"office's UTC offset substracted from it" do
15+
travel_to DateTime.new(2020, 10, 1, 10, 0, 0) do
16+
office = build(:office)
17+
end
18+
end
19+
end
20+
end
21+
describe "#past?" do
22+
context "more than 15 min have passed since appointment start time" do
23+
it "returns true" do # <== HERE
24+
end
25+
end
26+
end
27+
EOM
28+
29+
search.call
30+
31+
expect(search.invalid_blocks.join.strip).to eq('it "returns true" do # <== HERE')
32+
end
33+
734
it "handles no spaces between blocks" do
835
search = CodeSearch.new(<<~EOM)
936
context "timezones workaround" do
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../spec_helper.rb"
4+
5+
module SyntaxErrorSearch
6+
RSpec.describe TrailingSlashJoin do
7+
8+
it "formats output" do
9+
code_lines = code_line_array(<<~'EOM')
10+
context "timezones workaround" do
11+
it "should receive a time in UTC format and return the time with the"\
12+
"office's UTC offset substracted from it" do
13+
travel_to DateTime.new(2020, 10, 1, 10, 0, 0) do
14+
office = build(:office)
15+
end
16+
end
17+
end
18+
EOM
19+
20+
out_code_lines = TrailingSlashJoin.new(code_lines: code_lines).call
21+
expect(
22+
DisplayCodeWithLineNumbers.new(
23+
lines: out_code_lines.select(&:visible?)
24+
).call
25+
).to eq(<<~'EOM'.indent(2))
26+
1 context "timezones workaround" do
27+
2 it "should receive a time in UTC format and return the time with the"\
28+
3 "office's UTC offset substracted from it" do
29+
4 travel_to DateTime.new(2020, 10, 1, 10, 0, 0) do
30+
5 office = build(:office)
31+
6 end
32+
7 end
33+
8 end
34+
EOM
35+
36+
expect(
37+
DisplayCodeWithLineNumbers.new(
38+
lines: out_code_lines.select(&:visible?),
39+
highlight_lines: out_code_lines[1]
40+
).call
41+
).to eq(<<~'EOM')
42+
1 context "timezones workaround" do
43+
❯ 2 it "should receive a time in UTC format and return the time with the"\
44+
❯ 3 "office's UTC offset substracted from it" do
45+
4 travel_to DateTime.new(2020, 10, 1, 10, 0, 0) do
46+
5 office = build(:office)
47+
6 end
48+
7 end
49+
8 end
50+
EOM
51+
end
52+
53+
it "trailing slash" do
54+
code_lines = code_line_array(<<~'EOM')
55+
it "trailing s" \
56+
"lash" do
57+
EOM
58+
59+
out_code_lines = TrailingSlashJoin.new(code_lines: code_lines).call
60+
61+
expect(code_lines[0]).to_not be_hidden
62+
expect(code_lines[1]).to be_hidden
63+
64+
expect(
65+
out_code_lines.join
66+
).to eq(code_lines.map(&:original).join)
67+
end
68+
69+
it "doesn't falsely identify trailing slashes" do
70+
code_lines = code_line_array(<<~'EOM')
71+
def formatters
72+
@formatters ||= {
73+
amazing_print: ->(obj) { obj.ai + "\n" },
74+
inspect: ->(obj) { obj.inspect + "\n" },
75+
json: ->(obj) { obj.to_json },
76+
marshal: ->(obj) { Marshal.dump(obj) },
77+
none: ->(_obj) { nil },
78+
pretty_json: ->(obj) { JSON.pretty_generate(obj) },
79+
pretty_print: ->(obj) { obj.pretty_inspect },
80+
puts: ->(obj) { require 'stringio'; sio = StringIO.new; sio.puts(obj); sio.string },
81+
to_s: ->(obj) { obj.to_s + "\n" },
82+
yaml: ->(obj) { obj.to_yaml },
83+
}
84+
end
85+
EOM
86+
87+
out_code_lines = TrailingSlashJoin.new(code_lines: code_lines).call
88+
expect(out_code_lines.join).to eq(code_lines.join)
89+
end
90+
end
91+
end

0 commit comments

Comments
 (0)