diff options
Diffstat (limited to 'spec/syntax_suggest')
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 |
