summaryrefslogtreecommitdiff
path: root/spec/syntax_suggest
diff options
context:
space:
mode:
Diffstat (limited to 'spec/syntax_suggest')
-rw-r--r--spec/syntax_suggest/integration/ruby_command_line_spec.rb30
-rw-r--r--spec/syntax_suggest/integration/syntax_suggest_spec.rb81
-rw-r--r--spec/syntax_suggest/spec_helper.rb21
-rw-r--r--spec/syntax_suggest/unit/api_spec.rb4
-rw-r--r--spec/syntax_suggest/unit/around_block_scan_spec.rb14
-rw-r--r--spec/syntax_suggest/unit/block_expand_spec.rb34
-rw-r--r--spec/syntax_suggest/unit/capture/before_after_keyword_ends_spec.rb47
-rw-r--r--spec/syntax_suggest/unit/capture/falling_indent_lines_spec.rb44
-rw-r--r--spec/syntax_suggest/unit/capture_code_context_spec.rb59
-rw-r--r--spec/syntax_suggest/unit/clean_document_spec.rb55
-rw-r--r--spec/syntax_suggest/unit/code_block_spec.rb2
-rw-r--r--spec/syntax_suggest/unit/code_line_spec.rb28
-rw-r--r--spec/syntax_suggest/unit/code_search_spec.rb82
-rw-r--r--spec/syntax_suggest/unit/core_ext_spec.rb32
-rw-r--r--spec/syntax_suggest/unit/display_invalid_blocks_spec.rb2
-rw-r--r--spec/syntax_suggest/unit/explain_syntax_spec.rb36
-rw-r--r--spec/syntax_suggest/unit/lex_all_spec.rb29
-rw-r--r--spec/syntax_suggest/unit/mini_stringio_spec.rb25
-rw-r--r--spec/syntax_suggest/unit/pathname_from_message_spec.rb9
-rw-r--r--spec/syntax_suggest/unit/scan_history_spec.rb114
-rw-r--r--spec/syntax_suggest/unit/visitor_spec.rb119
21 files changed, 697 insertions, 170 deletions
diff --git a/spec/syntax_suggest/integration/ruby_command_line_spec.rb b/spec/syntax_suggest/integration/ruby_command_line_spec.rb
index 61102dad2a..02354ceff0 100644
--- a/spec/syntax_suggest/integration/ruby_command_line_spec.rb
+++ b/spec/syntax_suggest/integration/ruby_command_line_spec.rb
@@ -9,7 +9,7 @@ module SyntaxSuggest
Dir.mktmpdir do |dir|
tmpdir = Pathname(dir)
script = tmpdir.join("script.rb")
- script.write <<~'EOM'
+ script.write <<~EOM
puts Kernel.private_methods
EOM
@@ -46,6 +46,24 @@ module SyntaxSuggest
end
end
+ # Since Ruby 3.2 includes syntax_suggest as a default gem, we might accidentally
+ # be requiring the default gem instead of this library under test. Assert that's
+ # not the case
+ it "tests current version of syntax_suggest" do
+ Dir.mktmpdir do |dir|
+ tmpdir = Pathname(dir)
+ script = tmpdir.join("script.rb")
+ contents = <<~'EOM'
+ puts "suggest_version is #{SyntaxSuggest::VERSION}"
+ EOM
+ script.write(contents)
+
+ out = `#{ruby} -I#{lib_dir} -rsyntax_suggest/version #{script} 2>&1`
+
+ expect(out).to include("suggest_version is #{SyntaxSuggest::VERSION}").once
+ end
+ end
+
it "detects require error and adds a message with auto mode" do
Dir.mktmpdir do |dir|
tmpdir = Pathname(dir)
@@ -76,8 +94,6 @@ module SyntaxSuggest
end
it "gem can be tested when executing on Ruby with default gem included" do
- skip if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2")
-
out = `#{ruby} -I#{lib_dir} -rsyntax_suggest -e "puts SyntaxError.instance_method(:detailed_message).source_location" 2>&1`
expect($?.success?).to be_truthy
@@ -85,8 +101,6 @@ module SyntaxSuggest
end
it "annotates a syntax error in Ruby 3.2+ when require is not used" do
- skip if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2")
-
Dir.mktmpdir do |dir|
tmpdir = Pathname(dir)
script = tmpdir.join("script.rb")
@@ -141,7 +155,7 @@ module SyntaxSuggest
Dir.mktmpdir do |dir|
tmpdir = Pathname(dir)
script = tmpdir.join("script.rb")
- script.write <<~'EOM'
+ script.write <<~EOM
$stderr = STDOUT
eval("def lol")
EOM
@@ -149,7 +163,7 @@ module SyntaxSuggest
out = `#{ruby} -I#{lib_dir} -rsyntax_suggest #{script} 2>&1`
expect($?.success?).to be_falsey
- expect(out).to include("(eval):1")
+ expect(out).to match(/\(eval.*\):1/)
expect(out).to_not include("SyntaxSuggest")
expect(out).to_not include("Could not find filename")
@@ -160,7 +174,7 @@ module SyntaxSuggest
Dir.mktmpdir do |dir|
tmpdir = Pathname(dir)
script = tmpdir.join("script.rb")
- script.write <<~'EOM'
+ script.write <<~EOM
break
EOM
diff --git a/spec/syntax_suggest/integration/syntax_suggest_spec.rb b/spec/syntax_suggest/integration/syntax_suggest_spec.rb
index bb50fafce7..de9bc5d38e 100644
--- a/spec/syntax_suggest/integration/syntax_suggest_spec.rb
+++ b/spec/syntax_suggest/integration/syntax_suggest_spec.rb
@@ -13,7 +13,7 @@ module SyntaxSuggest
io = StringIO.new
- benchmark = Benchmark.measure do
+ benchmark_measure do
debug_perf do
SyntaxSuggest.call(
io: io,
@@ -21,11 +21,11 @@ module SyntaxSuggest
filename: file
)
end
- debug_display(io.string)
- debug_display(benchmark)
end
- expect(io.string).to include(<<~'EOM')
+ debug_display(io.string)
+
+ expect(io.string).to include(<<~EOM)
6 class SyntaxTree < Ripper
170 def self.parse(source)
174 end
@@ -41,7 +41,7 @@ module SyntaxSuggest
io = StringIO.new
debug_perf do
- benchmark = Benchmark.measure do
+ benchmark_measure do
SyntaxSuggest.call(
io: io,
source: file.read,
@@ -49,11 +49,10 @@ module SyntaxSuggest
)
end
debug_display(io.string)
- debug_display(benchmark)
end
expect(io.string).to_not include("def ruby_install_binstub_path")
- expect(io.string).to include(<<~'EOM')
+ expect(io.string).to include(<<~EOM)
> 1067 def add_yarn_binary
> 1068 return [] if yarn_preinstalled?
> 1069 |
@@ -71,7 +70,7 @@ module SyntaxSuggest
)
debug_display(io.string)
- expect(io.string).to include(<<~'EOM')
+ expect(io.string).to include(<<~EOM)
1 Rails.application.routes.draw do
> 113 namespace :admin do
> 116 match "/foobar(*path)", via: :all, to: redirect { |_params, req|
@@ -90,7 +89,7 @@ module SyntaxSuggest
)
debug_display(io.string)
- expect(io.string).to include(<<~'EOM')
+ expect(io.string).to include(<<~EOM)
1 describe "webmock tests" do
22 it "body" do
27 query = Cutlass::FunctionQuery.new(
@@ -112,12 +111,9 @@ module SyntaxSuggest
)
debug_display(io.string)
- expect(io.string).to include(<<~'EOM')
+ expect(io.string).to include(<<~EOM)
5 module DerailedBenchmarks
6 class RequireTree
- 7 REQUIRED_BY = {}
- 9 attr_reader :name
- 10 attr_writer :cost
> 13 def initialize(name)
> 18 def self.reset!
> 25 end
@@ -160,7 +156,6 @@ module SyntaxSuggest
out = io.string
expect(out).to include(<<~EOM)
16 class Rexe
- 18 VERSION = '1.5.1'
> 77 class Lookups
> 140 def format_requires
> 148 end
@@ -169,7 +164,7 @@ module SyntaxSuggest
end
it "ambiguous end" do
- source = <<~'EOM'
+ source = <<~EOM
def call # 0
print "lol" # 1
end # one # 2
@@ -189,7 +184,7 @@ module SyntaxSuggest
end
it "simple regression" do
- source = <<~'EOM'
+ source = <<~EOM
class Dog
def bark
puts "woof"
@@ -207,5 +202,59 @@ module SyntaxSuggest
> 4 end
EOM
end
+
+ it "multi-line chain with missing paren" do
+ source = <<~EOM
+ class Dog
+ def bark
+ User
+ .where(name: "schneems"
+ .first
+ end
+ end
+ EOM
+ io = StringIO.new
+ SyntaxSuggest.call(
+ io: io,
+ source: source
+ )
+ out = io.string
+ expect(out).to include(<<~EOM)
+ > 3 User
+ > 4 .where(name: "schneems"
+ > 5 .first
+ EOM
+ end
+
+ it "empty else" do
+ source = <<~EOM
+ class Foo
+ def foo
+ if cond?
+ foo
+ else
+
+ end
+ end
+
+ # ...
+
+ def bar
+ if @recv
+ end_is_missing_here
+ end
+ end
+ EOM
+
+ io = StringIO.new
+ SyntaxSuggest.call(
+ io: io,
+ source: source
+ )
+ out = io.string
+ expect(out).to include(<<~EOM)
+ end_is_missing_here
+ EOM
+ end
end
end
diff --git a/spec/syntax_suggest/spec_helper.rb b/spec/syntax_suggest/spec_helper.rb
index 67d401888b..c0aaf0f4f7 100644
--- a/spec/syntax_suggest/spec_helper.rb
+++ b/spec/syntax_suggest/spec_helper.rb
@@ -3,7 +3,10 @@
require "bundler/setup"
require "syntax_suggest/api"
-require "benchmark"
+begin
+ require "benchmark"
+rescue LoadError
+end
require "tempfile"
RSpec.configure do |config|
@@ -16,6 +19,12 @@ RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = :expect
end
+
+ if config.color_mode == :automatic
+ if config.color_enabled? && ((ENV["TERM"] == "dumb") || ENV["NO_COLOR"]&.slice(0))
+ config.color_mode = :off
+ end
+ end
end
# Used for debugging modifications to
@@ -76,6 +85,16 @@ def debug_perf
end
end
+def benchmark_measure
+ raise "No block given" unless block_given?
+
+ if defined?(::Benchmark)
+ debug_display(Benchmark.measure { yield })
+ else
+ yield
+ end
+end
+
def run!(cmd, raise_on_nonzero_exit: true)
out = `#{cmd} 2>&1`
raise "Command: #{cmd} failed: #{out}" if !$?.success? && raise_on_nonzero_exit
diff --git a/spec/syntax_suggest/unit/api_spec.rb b/spec/syntax_suggest/unit/api_spec.rb
index 079a91e46d..9299a17bee 100644
--- a/spec/syntax_suggest/unit/api_spec.rb
+++ b/spec/syntax_suggest/unit/api_spec.rb
@@ -63,8 +63,6 @@ module SyntaxSuggest
end
it "respects highlight API" do
- skip if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2")
-
core_ext_file = lib_dir.join("syntax_suggest").join("core_ext.rb")
require_relative core_ext_file
@@ -85,8 +83,6 @@ module SyntaxSuggest
end
it "can be disabled via falsey kwarg" do
- skip if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2")
-
core_ext_file = lib_dir.join("syntax_suggest").join("core_ext.rb")
require_relative core_ext_file
diff --git a/spec/syntax_suggest/unit/around_block_scan_spec.rb b/spec/syntax_suggest/unit/around_block_scan_spec.rb
index 6053c3947e..6c940a5919 100644
--- a/spec/syntax_suggest/unit/around_block_scan_spec.rb
+++ b/spec/syntax_suggest/unit/around_block_scan_spec.rb
@@ -5,7 +5,7 @@ require_relative "../spec_helper"
module SyntaxSuggest
RSpec.describe AroundBlockScan do
it "continues scan from last location even if scan is false" do
- source = <<~'EOM'
+ source = <<~EOM
print 'omg'
print 'lol'
print 'haha'
@@ -13,7 +13,7 @@ module SyntaxSuggest
code_lines = CodeLine.from_source(source)
block = CodeBlock.new(lines: code_lines[1])
expand = AroundBlockScan.new(code_lines: code_lines, block: block)
- .scan_neighbors
+ .scan_neighbors_not_empty
expect(expand.code_block.to_s).to eq(source)
expand.scan_while { |line| false }
@@ -104,8 +104,8 @@ module SyntaxSuggest
expand = AroundBlockScan.new(code_lines: code_lines, block: block)
expand.scan_while { true }
- expect(expand.before_index).to eq(0)
- expect(expand.after_index).to eq(6)
+ expect(expand.lines.first.index).to eq(0)
+ expect(expand.lines.last.index).to eq(6)
expect(expand.code_block.to_s).to eq(source_string)
end
@@ -149,9 +149,9 @@ module SyntaxSuggest
block = CodeBlock.new(lines: code_lines[3])
expand = AroundBlockScan.new(code_lines: code_lines, block: block)
- expand.skip(:empty?)
- expand.skip(:hidden?)
- expand.scan_neighbors
+ expand.force_add_empty
+ expand.force_add_hidden
+ expand.scan_neighbors_not_empty
expect(expand.code_block.to_s).to eq(<<~EOM.indent(4))
diff --git a/spec/syntax_suggest/unit/block_expand_spec.rb b/spec/syntax_suggest/unit/block_expand_spec.rb
index ba0b0457a1..fde0360775 100644
--- a/spec/syntax_suggest/unit/block_expand_spec.rb
+++ b/spec/syntax_suggest/unit/block_expand_spec.rb
@@ -4,6 +4,36 @@ require_relative "../spec_helper"
module SyntaxSuggest
RSpec.describe BlockExpand do
+ it "empty line in methods" do
+ source_string = <<~EOM
+ class Dog # index 0
+ def bark # index 1
+
+ end # index 3
+
+ def sit # index 5
+ print "sit" # index 6
+ end # index 7
+ end # index 8
+ end # extra end
+ EOM
+
+ code_lines = code_line_array(source_string)
+
+ sit = code_lines[4..7]
+ sit.each(&:mark_invisible)
+
+ block = CodeBlock.new(lines: sit)
+ expansion = BlockExpand.new(code_lines: code_lines)
+ block = expansion.expand_neighbors(block)
+
+ expect(block.to_s).to eq(<<~EOM.indent(2))
+ def bark # index 1
+
+ end # index 3
+ EOM
+ end
+
it "captures multiple empty and hidden lines" do
source_string = <<~EOM
def foo
@@ -116,7 +146,7 @@ module SyntaxSuggest
EOM
end
- it "expand until next boundry (indentation)" do
+ it "expand until next boundary (indentation)" do
source_string = <<~EOM
describe "what" do
Foo.call
@@ -158,7 +188,7 @@ module SyntaxSuggest
EOM
end
- it "expand until next boundry (empty lines)" do
+ it "expand until next boundary (empty lines)" do
source_string = <<~EOM
describe "what" do
end
diff --git a/spec/syntax_suggest/unit/capture/before_after_keyword_ends_spec.rb b/spec/syntax_suggest/unit/capture/before_after_keyword_ends_spec.rb
new file mode 100644
index 0000000000..09f8d90d33
--- /dev/null
+++ b/spec/syntax_suggest/unit/capture/before_after_keyword_ends_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require_relative "../../spec_helper"
+
+module SyntaxSuggest
+ RSpec.describe Capture::BeforeAfterKeywordEnds do
+ it "before after keyword ends" do
+ source = <<~EOM
+ def nope
+ print 'not me'
+ end
+
+ def lol
+ print 'lol'
+ end
+
+ def hello # 8
+
+ def yolo
+ print 'haha'
+ end
+
+ def nada
+ print 'nope'
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[8])
+
+ expect(block.to_s).to include("def hello")
+
+ lines = Capture::BeforeAfterKeywordEnds.new(
+ block: block,
+ code_lines: code_lines
+ ).call
+ lines.sort!
+
+ expect(lines.join).to include(<<~EOM)
+ def lol
+ end
+ def yolo
+ end
+ EOM
+ end
+ end
+end
diff --git a/spec/syntax_suggest/unit/capture/falling_indent_lines_spec.rb b/spec/syntax_suggest/unit/capture/falling_indent_lines_spec.rb
new file mode 100644
index 0000000000..ed2265539a
--- /dev/null
+++ b/spec/syntax_suggest/unit/capture/falling_indent_lines_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require_relative "../../spec_helper"
+
+module SyntaxSuggest
+ RSpec.describe Capture::FallingIndentLines do
+ it "on_falling_indent" do
+ source = <<~EOM
+ class OH
+ def lol
+ print 'lol
+ end
+
+ def hello
+ it "foo" do
+ end
+
+ def yolo
+ print 'haha'
+ end
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[6])
+
+ lines = []
+ Capture::FallingIndentLines.new(
+ block: block,
+ code_lines: code_lines
+ ).call do |line|
+ lines << line
+ end
+ lines.sort!
+
+ expect(lines.join).to eq(<<~EOM)
+ class OH
+ def hello
+ end
+ end
+ EOM
+ end
+ end
+end
diff --git a/spec/syntax_suggest/unit/capture_code_context_spec.rb b/spec/syntax_suggest/unit/capture_code_context_spec.rb
index e1bc281c13..d9379d0ce7 100644
--- a/spec/syntax_suggest/unit/capture_code_context_spec.rb
+++ b/spec/syntax_suggest/unit/capture_code_context_spec.rb
@@ -4,8 +4,34 @@ require_relative "../spec_helper"
module SyntaxSuggest
RSpec.describe CaptureCodeContext do
+ it "capture_before_after_kws two" do
+ source = <<~EOM
+ class OH
+
+ def hello
+
+ def hai
+ end
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[2])
+
+ display = CaptureCodeContext.new(
+ blocks: [block],
+ code_lines: code_lines
+ )
+ display.capture_before_after_kws(block)
+ expect(display.sorted_lines.join).to eq(<<~EOM.indent(2))
+ def hello
+ def hai
+ end
+ EOM
+ end
+
it "capture_before_after_kws" do
- source = <<~'EOM'
+ source = <<~EOM
def sit
end
@@ -16,14 +42,15 @@ module SyntaxSuggest
EOM
code_lines = CleanDocument.new(source: source).call.lines
- block = CodeBlock.new(lines: code_lines[0])
+ block = CodeBlock.new(lines: code_lines[3])
display = CaptureCodeContext.new(
blocks: [block],
code_lines: code_lines
)
- lines = display.call
- expect(lines.join).to eq(<<~'EOM')
+
+ lines = display.capture_before_after_kws(block).sort
+ expect(lines.join).to eq(<<~EOM)
def sit
end
def bark
@@ -33,7 +60,7 @@ module SyntaxSuggest
end
it "handles ambiguous end" do
- source = <<~'EOM'
+ source = <<~EOM
def call # 0
print "lol" # 1
end # one # 2
@@ -52,7 +79,7 @@ module SyntaxSuggest
lines = lines.sort.map(&:original)
- expect(lines.join).to eq(<<~'EOM')
+ expect(lines.join).to eq(<<~EOM)
def call # 0
end # one # 2
end # two # 3
@@ -67,7 +94,7 @@ module SyntaxSuggest
code_lines = CleanDocument.new(source: source).call.lines
code_lines[0..75].each(&:mark_invisible)
- code_lines[77..-1].each(&:mark_invisible)
+ code_lines[77..].each(&:mark_invisible)
expect(code_lines.join.strip).to eq("class Lookups")
block = CodeBlock.new(lines: code_lines[76..149])
@@ -79,7 +106,7 @@ module SyntaxSuggest
lines = display.call
lines = lines.sort.map(&:original)
- expect(lines.join).to include(<<~'EOM'.indent(2))
+ expect(lines.join).to include(<<~EOM.indent(2))
class Lookups
def format_requires
end
@@ -87,7 +114,7 @@ module SyntaxSuggest
end
it "shows ends of captured block" do
- source = <<~'EOM'
+ source = <<~EOM
class Dog
def bark
puts "woof"
@@ -96,7 +123,7 @@ module SyntaxSuggest
code_lines = CleanDocument.new(source: source).call.lines
block = CodeBlock.new(lines: code_lines)
- code_lines[1..-1].each(&:mark_invisible)
+ code_lines[1..].each(&:mark_invisible)
expect(block.to_s.strip).to eq("class Dog")
@@ -105,7 +132,7 @@ module SyntaxSuggest
code_lines: code_lines
)
lines = display.call.sort.map(&:original)
- expect(lines.join).to eq(<<~'EOM')
+ expect(lines.join).to eq(<<~EOM)
class Dog
def bark
end
@@ -113,7 +140,7 @@ module SyntaxSuggest
end
it "captures surrounding context on falling indent" do
- source = <<~'EOM'
+ source = <<~EOM
class Blerg
end
@@ -137,7 +164,7 @@ module SyntaxSuggest
code_lines: code_lines
)
lines = display.call.sort.map(&:original)
- expect(lines.join).to eq(<<~'EOM')
+ expect(lines.join).to eq(<<~EOM)
class OH
def hello
it "foo" do
@@ -147,7 +174,7 @@ module SyntaxSuggest
end
it "captures surrounding context on same indent" do
- source = <<~'EOM'
+ source = <<~EOM
class Blerg
end
class OH
@@ -173,7 +200,7 @@ module SyntaxSuggest
code_lines = CleanDocument.new(source: source).call.lines
block = CodeBlock.new(lines: code_lines[7..10])
- expect(block.to_s).to eq(<<~'EOM'.indent(2))
+ expect(block.to_s).to eq(<<~EOM.indent(2))
def lol
end
@@ -190,7 +217,7 @@ module SyntaxSuggest
lines: lines
).call
- expect(out).to eq(<<~'EOM'.indent(2))
+ expect(out).to eq(<<~EOM.indent(2))
3 class OH
8 def lol
9 end
diff --git a/spec/syntax_suggest/unit/clean_document_spec.rb b/spec/syntax_suggest/unit/clean_document_spec.rb
index 4fb79efd62..3d83bd13c0 100644
--- a/spec/syntax_suggest/unit/clean_document_spec.rb
+++ b/spec/syntax_suggest/unit/clean_document_spec.rb
@@ -8,7 +8,7 @@ module SyntaxSuggest
source = fixtures_dir.join("this_project_extra_def.rb.txt").read
code_lines = CleanDocument.new(source: source).call.lines
- expect(code_lines[18 - 1].to_s).to eq(<<-'EOL')
+ expect(code_lines[18 - 1].to_s).to eq(<<-EOL)
@io.puts <<~EOM
SyntaxSuggest: A syntax error was detected
@@ -54,7 +54,7 @@ module SyntaxSuggest
DisplayCodeWithLineNumbers.new(
lines: lines
).call
- ).to eq(<<~'EOM'.indent(2))
+ ).to eq(<<~EOM.indent(2))
1 User
2 .where(name: 'schneems')
3 .first
@@ -65,13 +65,31 @@ module SyntaxSuggest
lines: lines,
highlight_lines: lines[0]
).call
- ).to eq(<<~'EOM')
+ ).to eq(<<~EOM)
> 1 User
> 2 .where(name: 'schneems')
> 3 .first
EOM
end
+ it "joins multi-line chained methods when separated by comments" do
+ source = <<~EOM
+ User.
+ # comment
+ where(name: 'schneems').
+ # another comment
+ first
+ EOM
+
+ doc = CleanDocument.new(source: source).join_consecutive!
+ code_lines = doc.lines
+
+ expect(code_lines[0].to_s.count($/)).to eq(5)
+ code_lines[1..].each do |line|
+ expect(line.to_s.strip.length).to eq(0)
+ end
+ end
+
it "helper method: take_while_including" do
source = <<~EOM
User
@@ -92,27 +110,10 @@ module SyntaxSuggest
# yolo
EOM
- out = CleanDocument.new(source: source).lines.join
- expect(out.to_s).to eq(<<~EOM)
-
- puts "what"
-
- EOM
- end
-
- it "whitespace: removes whitespace" do
- source = " \n" + <<~EOM
- puts "what"
- EOM
-
- out = CleanDocument.new(source: source).lines.join
- expect(out.to_s).to eq(<<~EOM)
-
- puts "what"
- EOM
-
- expect(source.lines.first.to_s).to_not eq("\n")
- expect(out.lines.first.to_s).to eq("\n")
+ lines = CleanDocument.new(source: source).lines
+ expect(lines[0].to_s).to eq($/)
+ expect(lines[1].to_s).to eq('puts "what"' + $/)
+ expect(lines[2].to_s).to eq(" " + $/)
end
it "trailing slash: does not join trailing do" do
@@ -138,7 +139,7 @@ module SyntaxSuggest
source = <<~'EOM'
context "timezones workaround" do
it "should receive a time in UTC format and return the time with the"\
- "office's UTC offset substracted from it" do
+ "office's UTC offset subtracted from it" do
travel_to DateTime.new(2020, 10, 1, 10, 0, 0) do
office = build(:office)
end
@@ -154,7 +155,7 @@ module SyntaxSuggest
).to eq(<<~'EOM'.indent(2))
1 context "timezones workaround" do
2 it "should receive a time in UTC format and return the time with the"\
- 3 "office's UTC offset substracted from it" do
+ 3 "office's UTC offset subtracted from it" do
4 travel_to DateTime.new(2020, 10, 1, 10, 0, 0) do
5 office = build(:office)
6 end
@@ -170,7 +171,7 @@ module SyntaxSuggest
).to eq(<<~'EOM')
1 context "timezones workaround" do
> 2 it "should receive a time in UTC format and return the time with the"\
- > 3 "office's UTC offset substracted from it" do
+ > 3 "office's UTC offset subtracted from it" do
4 travel_to DateTime.new(2020, 10, 1, 10, 0, 0) do
5 office = build(:office)
6 end
diff --git a/spec/syntax_suggest/unit/code_block_spec.rb b/spec/syntax_suggest/unit/code_block_spec.rb
index 3ab2751b27..dfea307668 100644
--- a/spec/syntax_suggest/unit/code_block_spec.rb
+++ b/spec/syntax_suggest/unit/code_block_spec.rb
@@ -33,7 +33,7 @@ module SyntaxSuggest
array = [block_2, block_1, block_0].sort
expect(array.last).to eq(block_2)
- block = CodeBlock.new(lines: CodeLine.new(line: " " * 8 + "foo", index: 4, lex: []))
+ block = CodeBlock.new(lines: CodeLine.new(line: " " * 8 + "foo", index: 4, tokens: [], consecutive: false))
array.prepend(block)
expect(array.max).to eq(block)
end
diff --git a/spec/syntax_suggest/unit/code_line_spec.rb b/spec/syntax_suggest/unit/code_line_spec.rb
index cc4fa48bc9..309d3d1c1e 100644
--- a/spec/syntax_suggest/unit/code_line_spec.rb
+++ b/spec/syntax_suggest/unit/code_line_spec.rb
@@ -5,7 +5,7 @@ require_relative "../spec_helper"
module SyntaxSuggest
RSpec.describe CodeLine do
it "bug in keyword detection" do
- lines = CodeLine.from_source(<<~'EOM')
+ lines = CodeLine.from_source(<<~EOM)
def to_json(*opts)
{
type: :module,
@@ -17,9 +17,7 @@ module SyntaxSuggest
end
it "supports endless method definitions" do
- skip("Unsupported ruby version") unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3")
-
- line = CodeLine.from_source(<<~'EOM').first
+ line = CodeLine.from_source(<<~EOM).first
def square(x) = x * x
EOM
@@ -28,7 +26,7 @@ module SyntaxSuggest
end
it "retains original line value, after being marked invisible" do
- line = CodeLine.from_source(<<~'EOM').first
+ line = CodeLine.from_source(<<~EOM).first
puts "lol"
EOM
expect(line.line).to match('puts "lol"')
@@ -38,7 +36,7 @@ module SyntaxSuggest
end
it "knows which lines can be joined" do
- code_lines = CodeLine.from_source(<<~'EOM')
+ code_lines = CodeLine.from_source(<<~EOM)
user = User.
where(name: 'schneems').
first
@@ -46,10 +44,11 @@ module SyntaxSuggest
EOM
# Indicates line 1 can join 2, 2 can join 3, but 3 won't join it's next line
- expect(code_lines.map(&:ignore_newline_not_beg?)).to eq([true, true, false, false])
+ expect(code_lines.map(&:consecutive?)).to eq([true, true, false, false])
end
+
it "trailing if" do
- code_lines = CodeLine.from_source(<<~'EOM')
+ code_lines = CodeLine.from_source(<<~EOM)
puts "lol" if foo
if foo
end
@@ -59,7 +58,7 @@ module SyntaxSuggest
end
it "trailing unless" do
- code_lines = CodeLine.from_source(<<~'EOM')
+ code_lines = CodeLine.from_source(<<~EOM)
puts "lol" unless foo
unless foo
end
@@ -130,14 +129,15 @@ module SyntaxSuggest
it "knows empty lines" do
code_lines = CodeLine.from_source(<<~EOM)
- # Not empty
+ # Comment only
+ foo # Inline comment
- # Not empty
+ bar
EOM
- expect(code_lines.map(&:empty?)).to eq([false, true, false])
- expect(code_lines.map(&:not_empty?)).to eq([true, false, true])
- expect(code_lines.map { |l| SyntaxSuggest.valid?(l) }).to eq([true, true, true])
+ expect(code_lines.map(&:empty?)).to eq([true, false, true, false])
+ expect(code_lines.map(&:not_empty?)).to eq([false, true, false, true])
+ expect(code_lines.map { |l| SyntaxSuggest.valid?(l) }).to eq([true, true, true, true])
end
it "counts indentations" do
diff --git a/spec/syntax_suggest/unit/code_search_spec.rb b/spec/syntax_suggest/unit/code_search_spec.rb
index f836ba36f3..502de14d7f 100644
--- a/spec/syntax_suggest/unit/code_search_spec.rb
+++ b/spec/syntax_suggest/unit/code_search_spec.rb
@@ -12,13 +12,13 @@ module SyntaxSuggest
search = CodeSearch.new(source)
search.call
- expect(search.invalid_blocks.join.strip).to eq(<<~'EOM'.strip)
+ expect(search.invalid_blocks.join.strip).to eq(<<~EOM.strip)
class Lookups
EOM
end
it "squished do regression" do
- source = <<~'EOM'
+ source = <<~EOM
def call
trydo
@@ -47,14 +47,14 @@ module SyntaxSuggest
search = CodeSearch.new(source)
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM'.indent(2))
+ expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2))
trydo
end # one
EOM
end
it "regression test ambiguous end" do
- source = <<~'EOM'
+ source = <<~EOM
def call # 0
print "lol" # 1
end # one # 2
@@ -64,13 +64,13 @@ module SyntaxSuggest
search = CodeSearch.new(source)
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM')
+ expect(search.invalid_blocks.join).to eq(<<~EOM)
end # two # 3
EOM
end
it "regression dog test" do
- source = <<~'EOM'
+ source = <<~EOM
class Dog
def bark
puts "woof"
@@ -79,7 +79,7 @@ module SyntaxSuggest
search = CodeSearch.new(source)
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM')
+ expect(search.invalid_blocks.join).to eq(<<~EOM)
class Dog
EOM
expect(search.invalid_blocks.first.lines.length).to eq(4)
@@ -99,7 +99,7 @@ module SyntaxSuggest
search = CodeSearch.new(source)
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM'.indent(2))
+ expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2))
Foo.call do |a
end # one
EOM
@@ -118,7 +118,7 @@ module SyntaxSuggest
search = CodeSearch.new(source)
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM'.indent(2))
+ expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2))
Foo.call do {
EOM
end
@@ -152,7 +152,7 @@ module SyntaxSuggest
end
it "handles no spaces between blocks" do
- source = <<~'EOM'
+ source = <<~EOM
context "foo bar" do
it "bars the foo" do
travel_to DateTime.new(2020, 10, 1, 10, 0, 0) do
@@ -172,7 +172,7 @@ module SyntaxSuggest
it "records debugging steps to a directory" do
Dir.mktmpdir do |dir|
dir = Pathname(dir)
- search = CodeSearch.new(<<~'EOM', record_dir: dir)
+ search = CodeSearch.new(<<~EOM, record_dir: dir)
class OH
def hello
def hai
@@ -193,7 +193,7 @@ module SyntaxSuggest
end
it "def with missing end" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
class OH
def hello
@@ -206,7 +206,7 @@ module SyntaxSuggest
expect(search.invalid_blocks.join.strip).to eq("def hello")
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
class OH
def hello
@@ -218,7 +218,7 @@ module SyntaxSuggest
expect(search.invalid_blocks.join.strip).to eq("def hello")
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
class OH
def hello
def hai
@@ -227,7 +227,7 @@ module SyntaxSuggest
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM'.indent(2))
+ expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2))
def hello
EOM
end
@@ -244,13 +244,13 @@ module SyntaxSuggest
highlight_lines: search.invalid_blocks.flat_map(&:lines)
).call
- expect(document).to include(<<~'EOM')
+ expect(document).to include(<<~EOM)
> 36 def filename
EOM
end
it "Format Code blocks real world example" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
require 'rails_helper'
RSpec.describe AclassNameHere, type: :worker do
@@ -291,7 +291,7 @@ module SyntaxSuggest
highlight_lines: search.invalid_blocks.flat_map(&:lines)
).call
- expect(document).to include(<<~'EOM')
+ expect(document).to include(<<~EOM)
1 require 'rails_helper'
2
3 RSpec.describe AclassNameHere, type: :worker do
@@ -308,7 +308,7 @@ module SyntaxSuggest
describe "needs improvement" do
describe "mis-matched-indentation" do
it "extra space before end" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
Foo.call
def foo
puts "lol"
@@ -318,14 +318,14 @@ module SyntaxSuggest
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM')
+ expect(search.invalid_blocks.join).to eq(<<~EOM)
Foo.call
end # two
EOM
end
it "stacked ends 2" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
def cat
blerg
end
@@ -339,7 +339,7 @@ module SyntaxSuggest
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM')
+ expect(search.invalid_blocks.join).to eq(<<~EOM)
Foo.call do
end # one
end # two
@@ -348,7 +348,7 @@ module SyntaxSuggest
end
it "stacked ends " do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
Foo.call
def foo
puts "lol"
@@ -358,14 +358,14 @@ module SyntaxSuggest
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM')
+ expect(search.invalid_blocks.join).to eq(<<~EOM)
Foo.call
end
EOM
end
it "missing space before end" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
Foo.call
def foo
@@ -377,7 +377,7 @@ module SyntaxSuggest
search.call
# expand-1 and expand-2 seem to be broken?
- expect(search.invalid_blocks.join).to eq(<<~'EOM')
+ expect(search.invalid_blocks.join).to eq(<<~EOM)
Foo.call
end
EOM
@@ -386,7 +386,7 @@ module SyntaxSuggest
end
it "returns syntax error in outer block without inner block" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
Foo.call
def foo
puts "lol"
@@ -396,27 +396,27 @@ module SyntaxSuggest
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM')
+ expect(search.invalid_blocks.join).to eq(<<~EOM)
Foo.call
end # two
EOM
end
it "doesn't just return an empty `end`" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
Foo.call
end
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM')
+ expect(search.invalid_blocks.join).to eq(<<~EOM)
Foo.call
end
EOM
end
it "finds multiple syntax errors" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
describe "hi" do
Foo.call
end
@@ -429,7 +429,7 @@ module SyntaxSuggest
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM'.indent(2))
+ expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2))
Foo.call
end
Bar.call
@@ -438,47 +438,47 @@ module SyntaxSuggest
end
it "finds a typo def" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
defzfoo
puts "lol"
end
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM')
+ expect(search.invalid_blocks.join).to eq(<<~EOM)
defzfoo
end
EOM
end
it "finds a mis-matched def" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
def foo
def blerg
end
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM'.indent(2))
+ expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2))
def blerg
EOM
end
it "finds a naked end" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
def foo
end # one
end # two
EOM
search.call
- expect(search.invalid_blocks.join).to eq(<<~'EOM'.indent(2))
+ expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2))
end # one
EOM
end
it "returns when no invalid blocks are found" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
def foo
puts 'lol'
end
@@ -489,14 +489,14 @@ module SyntaxSuggest
end
it "expands frontier by eliminating valid lines" do
- search = CodeSearch.new(<<~'EOM')
+ search = CodeSearch.new(<<~EOM)
def foo
puts 'lol'
end
EOM
search.create_blocks_from_untracked_lines
- expect(search.code_lines.join).to eq(<<~'EOM')
+ expect(search.code_lines.join).to eq(<<~EOM)
def foo
end
EOM
diff --git a/spec/syntax_suggest/unit/core_ext_spec.rb b/spec/syntax_suggest/unit/core_ext_spec.rb
new file mode 100644
index 0000000000..d579cc8dc4
--- /dev/null
+++ b/spec/syntax_suggest/unit/core_ext_spec.rb
@@ -0,0 +1,32 @@
+require_relative "../spec_helper"
+
+module SyntaxSuggest
+ RSpec.describe "Core extension" do
+ it "SyntaxError monkepatch ensures there is a newline to the end of the file" do
+ Dir.mktmpdir do |dir|
+ tmpdir = Pathname(dir)
+ file = tmpdir.join("file.rb")
+ file.write(<<~EOM.strip)
+ print 'no newline
+ EOM
+
+ core_ext_file = lib_dir.join("syntax_suggest").join("core_ext")
+ require_relative core_ext_file
+
+ original_message = "blerg"
+ error = SyntaxError.new(original_message)
+ def error.set_tmp_path_for_testing=(path)
+ @tmp_path_for_testing = path
+ end
+ error.set_tmp_path_for_testing = file
+ def error.path
+ @tmp_path_for_testing
+ end
+
+ detailed = error.detailed_message(highlight: false, syntax_suggest: true)
+ expect(detailed).to include("'no newline\n#{original_message}")
+ expect(detailed).to_not include("print 'no newline#{original_message}")
+ end
+ end
+ end
+end
diff --git a/spec/syntax_suggest/unit/display_invalid_blocks_spec.rb b/spec/syntax_suggest/unit/display_invalid_blocks_spec.rb
index 2c59d9dc78..b11d7d242e 100644
--- a/spec/syntax_suggest/unit/display_invalid_blocks_spec.rb
+++ b/spec/syntax_suggest/unit/display_invalid_blocks_spec.rb
@@ -144,6 +144,7 @@ module SyntaxSuggest
expect(io.string).to include([
" 1 class OH",
"> 2 def hello",
+ " 3 def hai",
" 4 end",
" 5 end",
""
@@ -162,6 +163,7 @@ module SyntaxSuggest
[
" 1 class OH",
["> 2 ", DisplayCodeWithLineNumbers::TERMINAL_HIGHLIGHT, " def hello"].join,
+ " 3 def hai",
" 4 end",
" 5 end",
""
diff --git a/spec/syntax_suggest/unit/explain_syntax_spec.rb b/spec/syntax_suggest/unit/explain_syntax_spec.rb
index 394981dcf6..7ddb32b8ea 100644
--- a/spec/syntax_suggest/unit/explain_syntax_spec.rb
+++ b/spec/syntax_suggest/unit/explain_syntax_spec.rb
@@ -14,12 +14,26 @@ module SyntaxSuggest
).call
expect(explain.missing).to eq([])
- expect(explain.errors.join).to include("unterminated string")
+ expect(explain.errors.join.strip).to_not be_empty
end
- it "handles %w[]" do
+ %w[w W i I].each do |type|
+ it "handles %#{type}-style array" do
+ source = <<~EOM
+ node.is_a?(Op) && %#{type}[| ||].include?(node.value) &&
+ EOM
+
+ explain = ExplainSyntax.new(
+ code_lines: CodeLine.from_source(source)
+ ).call
+
+ expect(explain.missing).to eq([])
+ end
+ end
+
+ it "handles %r-style regexp" do
source = <<~EOM
- node.is_a?(Op) && %w[| ||].include?(node.value) &&
+ node.is_a?(Op) && %r{| ||}.include?(node.value) &&
EOM
explain = ExplainSyntax.new(
@@ -29,6 +43,20 @@ module SyntaxSuggest
expect(explain.missing).to eq([])
end
+ ["", "q", "Q"].each do |type|
+ it "handles %#{type}-style string" do
+ source = <<~EOM
+ node.is_a?(Op) && %#{type}(| ||).include?(node.value) &&
+ EOM
+
+ explain = ExplainSyntax.new(
+ code_lines: CodeLine.from_source(source)
+ ).call
+
+ expect(explain.missing).to eq([])
+ end
+ end
+
it "doesn't falsely identify strings or symbols as critical chars" do
source = <<~EOM
a = ['(', '{', '[', '|']
@@ -191,7 +219,7 @@ module SyntaxSuggest
).call
expect(explain.missing).to eq([])
- expect(explain.errors).to eq(RipperErrors.new(source).call.errors)
+ expect(explain.errors).to eq(GetParseErrors.errors(source))
end
it "handles an unexpected rescue" do
diff --git a/spec/syntax_suggest/unit/lex_all_spec.rb b/spec/syntax_suggest/unit/lex_all_spec.rb
deleted file mode 100644
index 0c0df7cfaa..0000000000
--- a/spec/syntax_suggest/unit/lex_all_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "../spec_helper"
-
-module SyntaxSuggest
- RSpec.describe "EndBlockParse" do
- it "finds blocks based on `end` keyword" do
- source = <<~EOM
- describe "cat" # 1
- Cat.call do # 2
- end # 3
- end # 4
- # 5
- it "dog" do # 6
- Dog.call do # 7
- end # 8
- end # 9
- EOM
-
- # raw_lex = Ripper.lex(source)
- # expect(raw_lex.to_s).to_not include("dog")
-
- lex = LexAll.new(source: source)
- expect(lex.map(&:token).to_s).to include("dog")
- expect(lex.first.line).to eq(1)
- expect(lex.last.line).to eq(9)
- end
- end
-end
diff --git a/spec/syntax_suggest/unit/mini_stringio_spec.rb b/spec/syntax_suggest/unit/mini_stringio_spec.rb
new file mode 100644
index 0000000000..75d94deae1
--- /dev/null
+++ b/spec/syntax_suggest/unit/mini_stringio_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require_relative "../spec_helper"
+
+module SyntaxSuggest
+ RSpec.describe "MiniStringIO" do
+ it "#puts with no inputs" do
+ io = MiniStringIO.new
+ io.puts
+ expect(io.string).to eq($/)
+ end
+
+ it "#puts with an input" do
+ io = MiniStringIO.new
+ io.puts "Hello"
+ expect(io.string).to eq(["Hello", $/].join)
+ end
+
+ it "#puts with an input with a newline" do
+ io = MiniStringIO.new
+ io.puts "Hello\n"
+ expect(io.string).to eq(["Hello\n", $/].join)
+ end
+ end
+end
diff --git a/spec/syntax_suggest/unit/pathname_from_message_spec.rb b/spec/syntax_suggest/unit/pathname_from_message_spec.rb
index 76756efda9..de58acebaa 100644
--- a/spec/syntax_suggest/unit/pathname_from_message_spec.rb
+++ b/spec/syntax_suggest/unit/pathname_from_message_spec.rb
@@ -43,6 +43,15 @@ module SyntaxSuggest
expect(file).to be_falsey
end
+ it "does not output error message on syntax error inside of an (eval at __FILE__:__LINE__)" do
+ message = "(eval at #{__FILE__}:#{__LINE__}):1: invalid multibyte char (UTF-8) (SyntaxError)\n"
+ io = StringIO.new
+ file = PathnameFromMessage.new(message, io: io).call.name
+
+ expect(io.string).to eq("")
+ expect(file).to be_falsey
+ end
+
it "does not output error message on syntax error inside of streamed code" do
# An example of streamed code is: $ echo "def foo" | ruby
message = "-:1: syntax error, unexpected end-of-input\n"
diff --git a/spec/syntax_suggest/unit/scan_history_spec.rb b/spec/syntax_suggest/unit/scan_history_spec.rb
new file mode 100644
index 0000000000..d8b0a54ba6
--- /dev/null
+++ b/spec/syntax_suggest/unit/scan_history_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require_relative "../spec_helper"
+
+module SyntaxSuggest
+ RSpec.describe ScanHistory do
+ it "retains commits" do
+ source = <<~EOM
+ class OH # 0
+ def lol # 1
+ print 'lol # 2
+ end # 3
+
+ def hello # 5
+ it "foo" do # 6
+ end # 7
+
+ def yolo # 8
+ print 'haha' # 9
+ end # 10
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[6])
+
+ scanner = ScanHistory.new(code_lines: code_lines, block: block)
+ scanner.scan(up: ->(_, _, _) { true }, down: ->(_, _, _) { true })
+
+ expect(scanner.changed?).to be_truthy
+ scanner.commit_if_changed
+ expect(scanner.changed?).to be_falsey
+
+ expect(scanner.lines).to eq(code_lines)
+
+ scanner.stash_changes # Assert does nothing if changes are already committed
+ expect(scanner.lines).to eq(code_lines)
+
+ scanner.revert_last_commit
+
+ expect(scanner.lines.join).to eq(code_lines[6].to_s)
+ end
+
+ it "is stashable" do
+ source = <<~EOM
+ class OH # 0
+ def lol # 1
+ print 'lol # 2
+ end # 3
+
+ def hello # 5
+ it "foo" do # 6
+ end # 7
+
+ def yolo # 8
+ print 'haha' # 9
+ end # 10
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[6])
+
+ scanner = ScanHistory.new(code_lines: code_lines, block: block)
+ scanner.scan(up: ->(_, _, _) { true }, down: ->(_, _, _) { true })
+
+ expect(scanner.lines).to eq(code_lines)
+ expect(scanner.changed?).to be_truthy
+ expect(scanner.next_up).to be_falsey
+ expect(scanner.next_down).to be_falsey
+
+ scanner.stash_changes
+
+ expect(scanner.changed?).to be_falsey
+
+ expect(scanner.next_up).to eq(code_lines[5])
+ expect(scanner.lines.join).to eq(code_lines[6].to_s)
+ expect(scanner.next_down).to eq(code_lines[7])
+ end
+
+ it "doesnt change if you dont't change it" do
+ source = <<~EOM
+ class OH # 0
+ def lol # 1
+ print 'lol # 2
+ end # 3
+
+ def hello # 5
+ it "foo" do # 6
+ end # 7
+
+ def yolo # 8
+ print 'haha' # 9
+ end # 10
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[6])
+
+ scanner = ScanHistory.new(code_lines: code_lines, block: block)
+
+ lines = scanner.lines
+ expect(scanner.changed?).to be_falsey
+ expect(scanner.next_up).to eq(code_lines[5])
+ expect(scanner.next_down).to eq(code_lines[7])
+
+ expect(scanner.stash_changes.lines).to eq(lines)
+ expect(scanner.revert_last_commit.lines).to eq(lines)
+
+ expect(scanner.scan(up: ->(_, _, _) { false }, down: ->(_, _, _) { false }).lines).to eq(lines)
+ end
+ end
+end
diff --git a/spec/syntax_suggest/unit/visitor_spec.rb b/spec/syntax_suggest/unit/visitor_spec.rb
new file mode 100644
index 0000000000..94eefd1e95
--- /dev/null
+++ b/spec/syntax_suggest/unit/visitor_spec.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require_relative "../spec_helper"
+
+module SyntaxSuggest
+ RSpec.describe Visitor do
+ def visit(source)
+ ast, _tokens = Prism.parse_lex(source).value
+ visitor = Visitor.new
+ visitor.visit(ast)
+ visitor
+ end
+
+ describe "#consecutive_lines" do
+ it "detects dot-leading multi-line chains" do
+ visitor = visit(<<~RUBY)
+ User
+ .where(name: "Earlopain")
+ .first
+ RUBY
+
+ expect(visitor.consecutive_lines).to eq(Set[1, 2])
+ end
+
+ it "detects dot-trailing multi-line chains" do
+ visitor = visit(<<~RUBY)
+ User.
+ where(name: "Earlopain").
+ first
+ RUBY
+
+ expect(visitor.consecutive_lines).to eq(Set[1, 2])
+ end
+
+ it "handles chains separated by comments" do
+ visitor = visit(<<~RUBY)
+ User.
+ # comment
+ where(name: "Earlopain").
+ # another comment
+ first
+ RUBY
+
+ # The AST sees through comments — every line except
+ # the last is consecutive regardless of interleaved comments.
+ expect(visitor.consecutive_lines).to eq(Set[1, 2, 3, 4])
+ end
+
+ it "returns empty for single-line calls" do
+ visitor = visit(<<~RUBY)
+ User.where(name: "Earlopain").first
+ RUBY
+
+ expect(visitor.consecutive_lines).to be_empty
+ end
+
+ it "returns empty when there is no method chain" do
+ visitor = visit(<<~RUBY)
+ puts "hello"
+ puts "world"
+ RUBY
+
+ expect(visitor.consecutive_lines).to be_empty
+ end
+
+ it "handles deeply nested chains" do
+ visitor = visit(<<~RUBY)
+ User
+ .where(name: "Earlopain")
+ .order(:created_at)
+ .limit(10)
+ .first
+ RUBY
+
+ expect(visitor.consecutive_lines).to eq(Set[1, 2, 3, 4])
+ end
+ end
+
+ describe "#endless_def_keyword_offsets" do
+ it "records the def location for endless methods" do
+ visitor = visit(<<~RUBY)
+ def square(x) = x * x
+ RUBY
+
+ expect(visitor.endless_def_keyword_offsets).to eq(Set[0])
+ end
+
+ it "does not record regular method definitions" do
+ visitor = visit(<<~RUBY)
+ def square(x)
+ x * x
+ end
+ RUBY
+
+ expect(visitor.endless_def_keyword_offsets).to be_empty
+ end
+
+ it "records multiple endless methods" do
+ visitor = visit(<<~RUBY)
+ def square(x) = x * x
+ def double(x) = x * 2
+ RUBY
+
+ expect(visitor.endless_def_keyword_offsets).to eq(Set[0, 22])
+ end
+
+ it "distinguishes endless from regular in the same source" do
+ visitor = visit(<<~RUBY)
+ def square(x) = x * x
+ def cube(x)
+ x * x * x
+ end
+ RUBY
+
+ expect(visitor.endless_def_keyword_offsets).to eq(Set[0])
+ end
+ end
+ end
+end