From 9d923f44739d5424d2738735b4d3c51ed77ed010 Mon Sep 17 00:00:00 2001 From: schneems Date: Sun, 8 Nov 2020 23:15:57 -0600 Subject: [PATCH 1/8] WIP WIP WIP --- lib/syntax_error_search/code_block.rb | 10 +- lib/syntax_error_search/code_frontier.rb | 53 ++++ lib/syntax_error_search/code_search.rb | 58 +++-- .../display_invalid_blocks.rb | 1 + spec/unit/code_block_spec.rb | 40 +++ spec/unit/code_search_spec.rb | 230 ++++++++++-------- 6 files changed, 274 insertions(+), 118 deletions(-) diff --git a/lib/syntax_error_search/code_block.rb b/lib/syntax_error_search/code_block.rb index 86386e1..2f53a11 100644 --- a/lib/syntax_error_search/code_block.rb +++ b/lib/syntax_error_search/code_block.rb @@ -28,7 +28,7 @@ module SyntaxErrorSearch class CodeBlock attr_reader :lines - def initialize(code_lines:, lines: []) + def initialize(code_lines: nil, lines: []) @lines = Array(lines) @code_lines = code_lines end @@ -200,6 +200,14 @@ def document_valid_without? block_without.valid? end + def valid_without? + block_without.valid? + end + + def invalid? + !valid? + end + def valid? SyntaxErrorSearch.valid?(self.to_s) end diff --git a/lib/syntax_error_search/code_frontier.rb b/lib/syntax_error_search/code_frontier.rb index 6921bfe..79cd042 100644 --- a/lib/syntax_error_search/code_frontier.rb +++ b/lib/syntax_error_search/code_frontier.rb @@ -140,6 +140,39 @@ module SyntaxErrorSearch # ] # # Once invalid blocks are found and filtered, then they can be passed to a formatter. + # + # + # + + class IndentScan + attr_reader :code_lines + + def initialize(code_lines: ) + @code_lines = code_lines + end + + def neighbors_from_top(top_line) + code_lines + .select {|l| l.index >= top_line.index } + .select {|l| l.not_empty? } + .select {|l| l.visible? } + .take_while {|l| l.indent >= top_line.indent } + end + + def each_neighbor_block(top_line) + neighbors = neighbors_from_top(top_line) + + until neighbors.empty? + lines = [neighbors.pop] + while (block = CodeBlock.new(lines: lines, code_lines: code_lines)) && block.invalid? && neighbors.any? + lines.prepend neighbors.pop + end + + yield block if block + end + end + end + class CodeFrontier def initialize(code_lines: ) @code_lines = code_lines @@ -183,6 +216,19 @@ def next_block? !@indent_hash.empty? end + + def indent_hash_indent + @indent_hash.keys.sort.last + end + + def next_indent_line + indent = @indent_hash.keys.sort.last + @indent_hash[indent]&.first + end + + def generate_blocks + end + def next_block indent = @indent_hash.keys.sort.last lines = @indent_hash[indent].first @@ -196,6 +242,13 @@ def next_block block end + def expand? + return false if @frontier.empty? + return true if @indent_hash.empty? + + @frontier.last.current_indent >= @indent_hash.keys.sort.last + end + # This method is responsible for determining if a new code # block should be generated instead of evaluating an already # existing block in the frontier diff --git a/lib/syntax_error_search/code_search.rb b/lib/syntax_error_search/code_search.rb index 23ce509..33a03fc 100644 --- a/lib/syntax_error_search/code_search.rb +++ b/lib/syntax_error_search/code_search.rb @@ -26,12 +26,13 @@ module SyntaxErrorSearch # class CodeSearch private; attr_reader :frontier; public - public; attr_reader :invalid_blocks, :record_dir + public; attr_reader :invalid_blocks, :record_dir, :code_lines def initialize(string, record_dir: ENV["SYNTAX_SEARCH_RECORD_DIR"]) if record_dir @time = Time.now.strftime('%Y-%m-%d-%H-%M-%s-%N') - @record_dir = Pathname(record_dir).join(@time) + @record_dir = Pathname(record_dir).join(@time).tap {|p| p.mkpath } + @write_count = 0 end @code_lines = string.lines.map.with_index do |line, i| CodeLine.new(line: line, index: i) @@ -40,13 +41,14 @@ def initialize(string, record_dir: ENV["SYNTAX_SEARCH_RECORD_DIR"]) @invalid_blocks = [] @name_tick = Hash.new {|hash, k| hash[k] = 0 } @tick = 0 + @scan = IndentScan.new(code_lines: @code_lines) end def record(block:, name: "record") return if !@record_dir @name_tick[name] += 1 - file = @record_dir.join("#{@tick}-#{name}-#{@name_tick[name]}.txt").tap {|p| p.dirname.mkpath } - file.open(mode: "a") do |f| + filename = "#{@write_count += 1}-#{name}-#{@name_tick[name]}.txt" + @record_dir.join(filename).open(mode: "a") do |f| display = DisplayInvalidBlocks.new( blocks: block, terminal: false @@ -55,37 +57,53 @@ def record(block:, name: "record") end end - def expand_frontier - return if !frontier.next_block? - block = frontier.next_block - record(block: block, name: "add") + def push_if_invalid(block, name: ) + frontier.register(block) + record(block: block, name: name) + if block.valid? block.lines.each(&:mark_invisible) - return expand_frontier + frontier << block else frontier << block end - block end - def search + def add_invalid_blocks + max_indent = frontier.next_indent_line&.indent + + while (line = frontier.next_indent_line) && (line.indent == max_indent) + neighbors = @scan.neighbors_from_top(frontier.next_indent_line) + + @scan.each_neighbor_block(frontier.next_indent_line) do |block| + record(block: block, name: "add") + if block.valid? + block.lines.each(&:mark_invisible) + end + end + + block = CodeBlock.new(lines: neighbors, code_lines: @code_lines) + push_if_invalid(block, name: "add") + end + end + + def expand_invalid_block block = frontier.pop + return unless block block.expand_until_next_boundry - record(block: block, name: "expand") - if block.valid? - block.lines.each(&:mark_invisible) - else - frontier << block - end + push_if_invalid(block, name: "expand") end def call until frontier.holds_all_syntax_errors? @tick += 1 - expand_frontier - break if frontier.holds_all_syntax_errors? # Need to check after every time something is added to frontier - search + + if frontier.expand? + expand_invalid_block + else + add_invalid_blocks + end end @invalid_blocks.concat(frontier.detect_invalid_blocks ) diff --git a/lib/syntax_error_search/display_invalid_blocks.rb b/lib/syntax_error_search/display_invalid_blocks.rb index c329161..ecd9476 100644 --- a/lib/syntax_error_search/display_invalid_blocks.rb +++ b/lib/syntax_error_search/display_invalid_blocks.rb @@ -60,6 +60,7 @@ def terminal_highlight def code_with_lines @code_lines.map do |line| next if line.hidden? + string = String.new("") if @invalid_line_hash[line] string << "❯ " diff --git a/spec/unit/code_block_spec.rb b/spec/unit/code_block_spec.rb index 7fbbcc3..5c19a09 100644 --- a/spec/unit/code_block_spec.rb +++ b/spec/unit/code_block_spec.rb @@ -4,6 +4,46 @@ module SyntaxErrorSearch RSpec.describe CodeBlock do + + it "expand until next boundry (indentation)" do + source_string = <<~EOM + def foo + Foo.call + end + end + EOM + + code_lines = code_line_array(source_string) + + scan = IndentScan.new(code_lines: code_lines) + neighbors = scan.neighbors_from_top(code_lines[1]) + + block = CodeBlock.new( + lines: neighbors.last, + code_lines: code_lines + ) + + expect(block.valid?).to be_falsey + expect(block.to_s).to eq(<<~EOM.indent(2)) + end + EOM + + frontier = [] + + scan.each_neighbor_block(code_lines[1]) do |block| + if block.valid? + block.lines.map(&:mark_valid) + else + frontier << block + end + end + + expect(frontier.join).to eq(<<~EOM.indent(2)) + Foo.call + end + EOM + end + it "expand until next boundry (indentation)" do source_string = <<~EOM describe "what" do diff --git a/spec/unit/code_search_spec.rb b/spec/unit/code_search_spec.rb index ce1c8f6..9ce3e71 100644 --- a/spec/unit/code_search_spec.rb +++ b/spec/unit/code_search_spec.rb @@ -19,7 +19,7 @@ def hai expect(search.record_dir.entries.map(&:to_s)).to include("1-add-1.txt") expect(search.record_dir.join("1-add-1.txt").read).to eq(<<~EOM.indent(2)) 1 class OH - ❯ 2 def hello + 2 def hello ❯ 3 def hai ❯ 4 end 5 end @@ -64,24 +64,98 @@ def hai expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2)) def hello - def hai - end EOM end + describe "real world cases" do + it "finds hanging def in this project" do + search = CodeSearch.new( + fixtures_dir.join("this_project_extra_def.rb.txt").read, + ) + + search.call + + blocks = search.invalid_blocks + io = StringIO.new + display = DisplayInvalidBlocks.new( + blocks: blocks, + io: io, + ) + display.call + # puts io.string + + expect(display.code_with_lines.strip_control_codes).to include(<<~EOM) + ❯ 36 def filename + EOM + end + + it "Format Code blocks real world example" do + search = CodeSearch.new(<<~EOM) + require 'rails_helper' + + RSpec.describe AclassNameHere, type: :worker do + describe "thing" do + context "when" do + let(:thing) { stuff } + let(:another_thing) { moarstuff } + subject { foo.new.perform(foo.id, true) } + + it "stuff" do + subject + + expect(foo.foo.foo).to eq(true) + end + end + end # line 16 accidental end, but valid block + + context "stuff" do + let(:thing) { create(:foo, foo: stuff) } + let(:another_thing) { create(:stuff) } + + subject { described_class.new.perform(foo.id, false) } + + it "more stuff" do + subject + + expect(foo.foo.foo).to eq(false) + end + end + end # mismatched due to 16 + end + EOM + search.call + + blocks = search.invalid_blocks + io = StringIO.new + display = DisplayInvalidBlocks.new(blocks: blocks, io: io, filename: "fake/spec/lol.rb") + display.call + # io.string + + expect(display.code_with_lines).to include(<<~EOM) + 1 require 'rails_helper' + 2 + 3 RSpec.describe AclassNameHere, type: :worker do + ❯ 12 + ❯ 30 end # mismatched due to 16 + 31 end + EOM + end + end + + # For code that's not perfectly formatted, we ideally want to do our best # These examples represent the results that exist today, but I would like to improve upon them describe "needs improvement" do describe "missing describe/do line" do it "blerg" do - code_lines = code_line_array fixtures_dir.join("this_project_extra_def.rb.txt").read - block = CodeBlock.new( - lines: code_lines[27], - code_lines: code_lines - ) - expect(block.to_s).to eq(<<~EOM.indent(8)) - file: \#{filename} - EOM + # code_lines = code_line_array fixtures_dir.join("this_project_extra_def.rb.txt").read + # block = CodeBlock.new( + # lines: code_lines[31], + # code_lines: code_lines + # ) + # expect(block.to_s).to eq(<<~EOM.indent(8)) + # \#{code_with_filename} + # EOM # puts block.before_line.to_s.inspect # puts block.before_line.to_s.split(/\S/).inspect @@ -91,86 +165,51 @@ def hai # puts block.after_line.to_s.split(/\S/).inspect # puts block.after_line.indent - # puts block.next_indent # puts block.expand_until_next_boundry end + end - it "this project" do - search = CodeSearch.new( - fixtures_dir.join("this_project_extra_def.rb.txt").read, - ) - + describe "mis-matched-indentation" do + it "extra space before end" do + search = CodeSearch.new(<<~EOM) + Foo.call + def foo + puts "lol" + puts "lol" + end # one + end # two + EOM search.call - blocks = search.invalid_blocks - io = StringIO.new - display = DisplayInvalidBlocks.new( - blocks: blocks, - io: io, - ) - display.call - # puts io.string - - expect(display.code_with_lines.strip_control_codes).to include(<<~EOM) - ❯ 36 def filename + # TODO improve here, grab the two end instead of one + expect(search.invalid_blocks.join).to eq(<<~EOM.indent(3)) + end # one EOM end - it "Format Code blocks real world example" do + it "stacked ends 2" do search = CodeSearch.new(<<~EOM) - require 'rails_helper' - - RSpec.describe AclassNameHere, type: :worker do - describe "thing" do - context "when" do - let(:thing) { stuff } - let(:another_thing) { moarstuff } - subject { foo.new.perform(foo.id, true) } - - it "stuff" do - subject - - expect(foo.foo.foo).to eq(true) - end - end - end # here - - context "stuff" do - let(:thing) { create(:foo, foo: stuff) } - let(:another_thing) { create(:stuff) } - - subject { described_class.new.perform(foo.id, false) } + def lol + blerg + end - it "more stuff" do - subject + Foo.call do + end # one + end # two - expect(foo.foo.foo).to eq(false) - end - end - end + def lol end EOM search.call - blocks = search.invalid_blocks - io = StringIO.new - display = DisplayInvalidBlocks.new(blocks: blocks, io: io, filename: "fake/spec/lol.rb") - display.call - # puts io.string - - expect(display.code_with_lines.strip_control_codes).to eq(<<~EOM) - 1 require 'rails_helper' - 2 - 3 RSpec.describe AclassNameHere, type: :worker do - ❯ 4 describe "thing" do - ❯ 16 end # here - ❯ 30 end - 31 end + expect(search.invalid_blocks.join).to eq(<<~EOM) + Foo.call do + end # one + end # two + EOM end - end - describe "mis-matched-indentation" do it "stacked ends " do search = CodeSearch.new(<<~EOM) Foo.call @@ -182,6 +221,7 @@ def foo EOM search.call + # TODO improve here, eliminate inner def foo expect(search.invalid_blocks.join).to eq(<<~EOM) Foo.call def foo @@ -190,26 +230,10 @@ def foo EOM end - it "extra space before end" do - search = CodeSearch.new(<<~EOM) - Foo.call - def foo - puts "lol" - puts "lol" - end - end - EOM - search.call - - # Does not include the line with the error Foo.call - expect(search.invalid_blocks.join).to eq(<<~EOM.indent(3)) - end - EOM - end - it "missing space before end" do search = CodeSearch.new(<<~EOM) Foo.call + def foo puts "lol" puts "lol" @@ -218,6 +242,7 @@ def foo EOM search.call + # expand-1 and expand-2 seem to be broken? expect(search.invalid_blocks.join).to eq(<<~EOM) Foo.call end @@ -232,14 +257,14 @@ def foo def foo puts "lol" puts "lol" - end - end + end # one + end # two EOM search.call expect(search.invalid_blocks.join).to eq(<<~EOM) Foo.call - end + end # two EOM end @@ -271,12 +296,9 @@ def foo EOM search.call - expect(search.invalid_blocks.join).to eq(<<~EOM.indent(0)) - describe "hi" do + expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2)) Foo.call end - end - Bar.call end EOM @@ -332,5 +354,19 @@ def foo expect(search.invalid_blocks).to eq([]) end + + it "expands frontier by eliminating valid lines" do + search = CodeSearch.new(<<~EOM) + def foo + puts 'lol' + end + EOM + search.add_invalid_blocks + + expect(search.code_lines.join).to eq(<<~EOM) + def foo + end + EOM + end end end From 0b5ca8d50544ec38ec53e343e1d6557408d5bb0d Mon Sep 17 00:00:00 2001 From: schneems Date: Tue, 10 Nov 2020 10:26:45 -0600 Subject: [PATCH 2/8] CLI interface --- exe/syntax_search | 73 +++++++++++++++++++ lib/syntax_error_search.rb | 15 +++- .../display_invalid_blocks.rb | 20 ++++- spec/unit/display_invalid_blocks_spec.rb | 20 +++++ spec/unit/exe_spec.rb | 51 +++++++++++++ syntax_error_search.gemspec | 3 +- 6 files changed, 176 insertions(+), 6 deletions(-) create mode 100755 exe/syntax_search create mode 100644 spec/unit/exe_spec.rb diff --git a/exe/syntax_search b/exe/syntax_search new file mode 100755 index 0000000..1e37d0f --- /dev/null +++ b/exe/syntax_search @@ -0,0 +1,73 @@ +#!/usr/bin/env ruby + +require 'pathname' +require "optparse" +require_relative "../lib/syntax_error_search.rb" + +options = {} +options[:terminal] = true +options[:record_dir] = ENV["SYNTAX_SEARCH_RECORD_DIR"] + +parser = OptionParser.new do |opts| + opts.banner = <<~EOM + Usage: syntax_search [options] + + Parses a ruby source file and searches for syntax error(s) unexpected `end', expecting end-of-input. + + Example: + + $ syntax_search dog.rb + + # ... + + ``` + 1 require 'animals' + 2 + ❯ 10 defdog + ❯ 15 end + ❯ 16 + 20 def cat + 22 end + ``` + + Env options: + + SYNTAX_SEARCH_RECORD_DIR= + + When enabled, records the steps used to search for a syntax error to the given directory + + Options: + EOM + + opts.on("--help", "Help - displays this message") do |v| + puts opts + exit + end + + opts.on("--record ", "When enabled, records the steps used to search for a syntax error to the given directory") do |v| + options[:record_dir] = v + end + + opts.on("--no-terminal", "Disable terminal highlighting") do |v| + options[:terminal] = false + end +end +parser.parse! + +file = ARGV[0] + +if file.nil? || file.empty? + # Display help if raw command + parser.parse! %w[--help] +end + +file = Pathname(file) + +$stderr.puts "Record dir: #{options[:record_dir]}" if options[:record_dir] + +SyntaxErrorSearch.call( + source: file.read, + filename: file.expand_path, + terminal: options[:terminal], + record_dir: options[:record_dir] +) diff --git a/lib/syntax_error_search.rb b/lib/syntax_error_search.rb index 6c7a7a3..bb01e98 100644 --- a/lib/syntax_error_search.rb +++ b/lib/syntax_error_search.rb @@ -1,14 +1,27 @@ # frozen_string_literal: true -require "syntax_error_search/version" +require_relative "syntax_error_search/version" require 'parser/current' require 'tmpdir' +require 'stringio' require 'pathname' module SyntaxErrorSearch class Error < StandardError; end + def self.call(source: , filename: , terminal: false, record_dir: nil) + search = CodeSearch.new(source, record_dir: record_dir).call + + blocks = search.invalid_blocks + DisplayInvalidBlocks.new( + blocks: blocks, + filename: filename, + terminal: terminal, + io: $stderr + ).call + end + # Used for counting spaces module SpaceCount def self.indent(string) diff --git a/lib/syntax_error_search/display_invalid_blocks.rb b/lib/syntax_error_search/display_invalid_blocks.rb index ecd9476..1a5db93 100644 --- a/lib/syntax_error_search/display_invalid_blocks.rb +++ b/lib/syntax_error_search/display_invalid_blocks.rb @@ -9,15 +9,30 @@ def initialize(blocks:, io: $stderr, filename: nil, terminal: false) @terminal = terminal @filename = filename @io = io + @blocks = Array(blocks) @lines = @blocks.map(&:lines).flatten - @code_lines = @blocks.first.code_lines - @digit_count = @code_lines.last.line_number.to_s.length + @code_lines = @blocks.first&.code_lines || [] + @digit_count = @code_lines.last&.line_number.to_s.length @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true } end def call + if @blocks.any? + found_invalid_blocks + else + @io.puts "Syntax OK" + end + self + end + + private def no_invalid_blocks + @io.puts <<~EOM + EOM + end + + private def found_invalid_blocks @io.puts <<~EOM SyntaxErrorSearch: A syntax error was detected @@ -33,7 +48,6 @@ def call #{indent(code_block)} EOM - self end def indent(string, with: " ") diff --git a/spec/unit/display_invalid_blocks_spec.rb b/spec/unit/display_invalid_blocks_spec.rb index 868ada9..ba2afa4 100644 --- a/spec/unit/display_invalid_blocks_spec.rb +++ b/spec/unit/display_invalid_blocks_spec.rb @@ -4,6 +4,26 @@ module SyntaxErrorSearch RSpec.describe DisplayInvalidBlocks do + it "works with valid code" do + syntax_string = <<~EOM + class OH + def hello + end + def hai + end + end + EOM + + io = StringIO.new + display = DisplayInvalidBlocks.new( + blocks: CodeSearch.new(syntax_string).call.invalid_blocks, + terminal: false, + io: io + ) + display.call + expect(io.string).to include("Syntax OK") + end + it "outputs to io when using `call`" do code_lines = code_line_array(<<~EOM) class OH diff --git a/spec/unit/exe_spec.rb b/spec/unit/exe_spec.rb new file mode 100644 index 0000000..3fcd41c --- /dev/null +++ b/spec/unit/exe_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative "../spec_helper.rb" + +module SyntaxErrorSearch + RSpec.describe "exe" do + def exe_path + root_dir.join("exe").join("syntax_search") + end + + def exe(cmd) + run!("#{exe_path} #{cmd}") + end + + def run!(cmd) + out = `#{cmd} 2>&1` + raise "Command: #{cmd} failed: #{out}" unless $?.success? + out + end + + it "parses valid code" do + ruby_file = exe_path + out = exe(ruby_file) + expect(out.strip).to eq("Syntax OK") + end + + it "parses invalid code" do + ruby_file = fixtures_dir.join("this_project_extra_def.rb.txt") + out = exe("#{ruby_file} --no-terminal") + + expect(out.strip).to include("A syntax error was detected") + expect(out.strip).to include("❯ 36 def filename") + end + + it "records search" do + Dir.mktmpdir do |dir| + dir = Pathname(dir) + tmp_dir = dir.join("tmp").tap(&:mkpath) + ruby_file = dir.join("file.rb") + ruby_file.write("def foo\n end\nend") + + expect(tmp_dir).to be_empty + + out = exe("#{ruby_file} --record #{tmp_dir}") + + expect(out.strip).to include("A syntax error was detected") + expect(tmp_dir).to_not be_empty + end + end + end +end diff --git a/syntax_error_search.gemspec b/syntax_error_search.gemspec index 786e3a6..4443803 100644 --- a/syntax_error_search.gemspec +++ b/syntax_error_search.gemspec @@ -3,7 +3,7 @@ require_relative 'lib/syntax_error_search/version' Gem::Specification.new do |spec| - spec.name = "syntax_error_search" + spec.name = "syntax_search" spec.version = SyntaxErrorSearch::VERSION spec.authors = ["schneems"] spec.email = ["richard.schneeman+foo@gmail.com"] @@ -14,7 +14,6 @@ Gem::Specification.new do |spec| spec.license = "MIT" spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0") - spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = "https://github.com/zombocom/syntax_error_search.git" From 6e7ee3a9e5340cde1588d456dcb1a2b8bb3f9eca Mon Sep 17 00:00:00 2001 From: schneems Date: Tue, 10 Nov 2020 13:16:38 -0600 Subject: [PATCH 3/8] "auto" and "fyi" files for configuring behvior --- Gemfile.lock | 4 +- README.md | 68 ++++++++------------- lib/syntax_error_search.rb | 17 ++++++ lib/syntax_error_search/auto.rb | 51 ++++++++++++++++ lib/syntax_error_search/fyi.rb | 7 +++ spec/spec_helper.rb | 10 +++ spec/syntax_error_search_spec.rb | 101 +++++++++++++++++++++++++++---- spec/unit/exe_spec.rb | 5 -- 8 files changed, 199 insertions(+), 64 deletions(-) create mode 100644 lib/syntax_error_search/auto.rb create mode 100644 lib/syntax_error_search/fyi.rb diff --git a/Gemfile.lock b/Gemfile.lock index bdaca7f..5029f2c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - syntax_error_search (0.1.0) + syntax_search (0.1.0) parser GEM @@ -32,7 +32,7 @@ PLATFORMS DEPENDENCIES rake (~> 12.0) rspec (~> 3.0) - syntax_error_search! + syntax_search! BUNDLED WITH 2.1.4 diff --git a/README.md b/README.md index 040f1ee..692912b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SyntaxErrorSearch +# SyntaxSearch Imagine you're programming, everything is awesome. You write code, it runs. Still awesome. You write more code, you save it, you run it and then: @@ -11,7 +11,26 @@ What happened? Likely you forgot a `def`, `do`, or maybe you deleted some code a What if I told you, that there was a library that helped find your missing `def`s and missing `do`s. What if instead of searching through hundreds of lines of source for the cause of your syntax error, there was a way to highlight just code in the file that contained syntax errors. ``` -# TODO example here +$ syntax_search path/to/file.rb + +SyntaxErrorSearch: A syntax error was detected + +This code has an unmatched `end` this is caused by either +missing a syntax keyword (`def`, `do`, etc.) or inclusion +of an extra `end` line + +file: path/to/file.rb +simplified: + + ``` + 1 require 'animals' + 2 + ❯ 10 defdog + ❯ 15 end + ❯ 16 + 20 def cat + 22 end + ``` ``` How much would you pay for such a library? A million, a billion, a trillion? Well friends, today is your lucky day because you can use this library today for free! @@ -21,7 +40,7 @@ How much would you pay for such a library? A million, a billion, a trillion? Wel Add this line to your application's Gemfile: ```ruby -gem 'syntax_error_search' +gem 'syntax_search', require: "syntax_search/auto" ``` And then execute: @@ -30,7 +49,7 @@ And then execute: Or install it yourself as: - $ gem install syntax_error_search + $ gem install syntax_search ## What does it do? @@ -55,48 +74,9 @@ By definition source code with a syntax error in it cannot be parsed, so we have At the end of the day we can't say where the syntax error is FOR SURE, but we can get pretty close. It sounds simple when spelled out like this, but it's a very complicated problem. Even when code is not correctly indented/formatted we can still likely tell you where to start searching even if we can't point at the exact problem line or location. -## Complicating concerns - -The biggest issue with searching for syntax errors stemming from "unexpected end" is that while the `end` in the code triggered the error, the problem actually came from somewhere else. Effectively these syntax errors always involve 2 or more lines of code, but one of those lines (without the end) may be syntatically valid on its own. For example: - -``` -1 Foo.call -2 -3 puts "lol -4 end -``` - -Here there's a missing `do` after `Foo.call` however `Foo.call` by itself is perfectly valid ruby code syntax. We don't find the error until we remove the `end` even though the problem is caused on the first line. This means that if our clode blocks aren't sliced totally correctly the error output might just point at: - -``` -4 end -``` - -Instead of: - -``` -1 Foo.call -4 end -``` - -Here's a similar issue, but with more `end` lines in the code to demonstrate. The same line of code causes the issue: - -``` -1 it "foo" do -2 Foo.call -3 -4 puts "lol -5 end -6 end -``` - -In this example we could make this code valid by either the end on line 5 or 6. As far as the program is concerned it's effectively got one too many ends and it won't care which you remove. The "correct" line to remove would be for the inner block, but it's hard to know this programatically. Whitespace can help guide us, but it's still a guess. - -One of the biggest challenges then is not finding code that can be removed to make the program syntatically correct (just remove an `end` and it works) but to also provide a reasonable guess as to the "pair" line that would have otherwise required an end (such as a `do` or a `def`). - ## How does this gem know when a syntax error occured in my code? -While I wish you hadn't asked: If you must know, we're monkey-patching require. It sounds scary, but bootsnap does essentially the same thing and we're way less invasive. +Right now the search isn't performed automatically when you get a syntax error. Instead we append a warning message letting you know how to test the file. Eventually we'll enable the seach by default instead of printing a warning message. To do both of these we have to monkeypatch `require` in the same way that bootsnap does. ## Development diff --git a/lib/syntax_error_search.rb b/lib/syntax_error_search.rb index bb01e98..974a886 100644 --- a/lib/syntax_error_search.rb +++ b/lib/syntax_error_search.rb @@ -9,6 +9,23 @@ module SyntaxErrorSearch class Error < StandardError; end + SEARCH_SOURCE_ON_ERROR_DEFAULT = true + + def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT) + raise e if !e.message.include?("unexpected `end'") + + filename = e.message.split(":").first + + if search_source_on_error + self.call( + source: Pathname(filename).read, + filename: filename, + terminal: true + ) + end + $stderr.puts "Run `$ syntax_search #{filename}` for more options\n\n" + raise e + end def self.call(source: , filename: , terminal: false, record_dir: nil) search = CodeSearch.new(source, record_dir: record_dir).call diff --git a/lib/syntax_error_search/auto.rb b/lib/syntax_error_search/auto.rb new file mode 100644 index 0000000..7ba0137 --- /dev/null +++ b/lib/syntax_error_search/auto.rb @@ -0,0 +1,51 @@ +require_relative "../syntax_error_search" + +# Monkey patch kernel to ensure that all `require` calls call the same +# method +module Kernel + alias_method :original_require, :require + alias_method :original_require_relative, :require_relative + alias_method :original_load, :load + + def load(file, wrap = false) + original_load(file) + rescue SyntaxError => e + SyntaxErrorSearch.handle_error(e) + end + + def require(file) + original_require(file) + rescue SyntaxError => e + SyntaxErrorSearch.handle_error(e) + end + + def require_relative(file) + if Pathname.new(file).absolute? + original_require file + else + original_require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path) + end + rescue SyntaxError => e + SyntaxErrorSearch.handle_error(e) + end +end + +# I honestly have no idea why this Object delegation is needed +# I keep staring at bootsnap and it doesn't have to do this +# is there a bug in their implementation they haven't caught or +# am I doing something different? +class Object + private + def load(path, wrap = false) + Kernel.load(path, wrap) + rescue SyntaxError => e + SyntaxErrorSearch.handle_error(e) + end + + def require(path) + Kernel.require(path) + rescue SyntaxError => e + SyntaxErrorSearch.handle_error(e) + end +end + diff --git a/lib/syntax_error_search/fyi.rb b/lib/syntax_error_search/fyi.rb new file mode 100644 index 0000000..609d246 --- /dev/null +++ b/lib/syntax_error_search/fyi.rb @@ -0,0 +1,7 @@ +require_relative "../syntax_error_search" + +require_relative "auto.rb" + +SyntaxErrorSearch.send(:remove_const, :SEARCH_SOURCE_ON_ERROR_DEFAULT) +SyntaxErrorSearch::SEARCH_SOURCE_ON_ERROR_DEFAULT = false + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 26ae3fe..67cfef4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,6 +19,10 @@ def spec_dir Pathname(__dir__) end +def lib_dir + root_dir.join("lib") +end + def root_dir spec_dir.join("..") end @@ -35,6 +39,12 @@ def code_line_array(string) code_lines end +def run!(cmd) + out = `#{cmd} 2>&1` + raise "Command: #{cmd} failed: #{out}" unless $?.success? + out +end + # Allows us to write cleaner tests since <<~EOM block quotes # strip off all leading indentation and we need it to be preserved # sometimes. diff --git a/spec/syntax_error_search_spec.rb b/spec/syntax_error_search_spec.rb index 64bb37d..3b82b9d 100644 --- a/spec/syntax_error_search_spec.rb +++ b/spec/syntax_error_search_spec.rb @@ -6,20 +6,14 @@ module SyntaxErrorSearch expect(SyntaxErrorSearch::VERSION).not_to be nil end - def ruby(script) - `ruby -I#{lib_dir} -rdid_you_do #{script} 2>&1` + def run_ruby(script) + `ruby -I#{lib_dir} -rsyntax_error_search/auto #{script} 2>&1` end - describe "foo" do - around(:each) do |example| - Dir.mktmpdir do |dir| - @tmpdir = Pathname(dir) - @script = @tmpdir.join("script.rb") - example.run - end - end - - it "blerg" do + it "detects require error and adds a message" do + Dir.mktmpdir do |dir| + @tmpdir = Pathname(dir) + @script = @tmpdir.join("script.rb") @script.write <<~EOM describe "things" do it "blerg" do @@ -38,8 +32,89 @@ def ruby(script) require_relative "./script.rb" EOM - # out = ruby(require_rb) + out = run_ruby(require_rb) # puts out + + expect(out).to include("Run `$ syntax_search") + end + end + + it "detects require error and adds a message when executed via bundler auto" do + Dir.mktmpdir do |dir| + dir = Pathname(dir) + gemfile = dir.join("Gemfile") + gemfile.write(<<~EOM) + gem "syntax_search", path: "#{root_dir}", require: "syntax_error_search/auto" + EOM + run!("BUNDLE_GEMFILE=#{gemfile} bundle install --local") + script = dir.join("script.rb") + script.write <<~EOM + describe "things" do + it "blerg" do + end + + it "flerg" + end + + it "zlerg" do + end + end + EOM + + Bundler.with_original_env do + require_rb = dir.join("require.rb") + require_rb.write <<~EOM + Bundler.require + + require_relative "./script.rb" + EOM + + out = `BUNDLE_GEMFILE=#{gemfile} bundle exec ruby #{require_rb} 2>&1` + + expect($?.success?).to be_falsey + expect(out).to include("This code has an unmatched") + expect(out).to include("Run `$ syntax_search") + end + end + end + + + it "detects require error and adds a message when executed via bundler auto" do + Dir.mktmpdir do |dir| + dir = Pathname(dir) + gemfile = dir.join("Gemfile") + gemfile.write(<<~EOM) + gem "syntax_search", path: "#{root_dir}", require: "syntax_error_search/fyi" + EOM + run!("BUNDLE_GEMFILE=#{gemfile} bundle install --local") + script = dir.join("script.rb") + script.write <<~EOM + describe "things" do + it "blerg" do + end + + it "flerg" + end + + it "zlerg" do + end + end + EOM + + Bundler.with_original_env do + require_rb = dir.join("require.rb") + require_rb.write <<~EOM + Bundler.require + + require_relative "./script.rb" + EOM + + out = `BUNDLE_GEMFILE=#{gemfile} bundle exec ruby #{require_rb} 2>&1` + + expect($?.success?).to be_falsey + expect(out).to_not include("This code has an unmatched") + expect(out).to include("Run `$ syntax_search") + end end end end diff --git a/spec/unit/exe_spec.rb b/spec/unit/exe_spec.rb index 3fcd41c..b0a1f3b 100644 --- a/spec/unit/exe_spec.rb +++ b/spec/unit/exe_spec.rb @@ -12,11 +12,6 @@ def exe(cmd) run!("#{exe_path} #{cmd}") end - def run!(cmd) - out = `#{cmd} 2>&1` - raise "Command: #{cmd} failed: #{out}" unless $?.success? - out - end it "parses valid code" do ruby_file = exe_path From 10acc6e05ad010c5814c54b9bfd8b71dcf91f275 Mon Sep 17 00:00:00 2001 From: schneems Date: Tue, 10 Nov 2020 13:58:44 -0600 Subject: [PATCH 4/8] SyntaxErrorSearch => SyntaxSearch --- Gemfile | 2 +- README.md | 4 ++-- bin/console | 2 +- exe/syntax_search | 2 +- lib/{syntax_error_search.rb => syntax_search.rb} | 12 ++++++------ lib/{syntax_error_search => syntax_search}/auto.rb | 2 +- .../code_block.rb | 0 .../code_frontier.rb | 0 .../code_line.rb | 0 .../code_search.rb | 0 .../display_invalid_blocks.rb | 0 lib/{syntax_error_search => syntax_search}/fyi.rb | 2 +- .../version.rb | 0 spec/spec_helper.rb | 2 +- spec/unit/exe_spec.rb | 1 - .../syntax_search_spec.rb} | 9 +++++---- syntax_error_search.gemspec => syntax_search.gemspec | 6 +++--- 17 files changed, 22 insertions(+), 22 deletions(-) rename lib/{syntax_error_search.rb => syntax_search.rb} (90%) rename lib/{syntax_error_search => syntax_search}/auto.rb (96%) rename lib/{syntax_error_search => syntax_search}/code_block.rb (100%) rename lib/{syntax_error_search => syntax_search}/code_frontier.rb (100%) rename lib/{syntax_error_search => syntax_search}/code_line.rb (100%) rename lib/{syntax_error_search => syntax_search}/code_search.rb (100%) rename lib/{syntax_error_search => syntax_search}/display_invalid_blocks.rb (100%) rename lib/{syntax_error_search => syntax_search}/fyi.rb (79%) rename lib/{syntax_error_search => syntax_search}/version.rb (100%) rename spec/{syntax_error_search_spec.rb => unit/syntax_search_spec.rb} (95%) rename syntax_error_search.gemspec => syntax_search.gemspec (87%) diff --git a/Gemfile b/Gemfile index bb44deb..57fb696 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" -# Specify your gem's dependencies in syntax_error_search.gemspec +# Specify your gem's dependencies in syntax_search.gemspec gemspec gem "rake", "~> 12.0" diff --git a/README.md b/README.md index 692912b..2d11e4b 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/syntax_error_search. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/syntax_error_search/blob/master/CODE_OF_CONDUCT.md). +Bug reports and pull requests are welcome on GitHub at https://github.com/zombocom/syntax_search. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/syntax_search/blob/master/CODE_OF_CONDUCT.md). ## License @@ -95,4 +95,4 @@ The gem is available as open source under the terms of the [MIT License](https:/ ## Code of Conduct -Everyone interacting in the SyntaxErrorSearch project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/syntax_error_search/blob/master/CODE_OF_CONDUCT.md). +Everyone interacting in the SyntaxErrorSearch project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/zombocom/syntax_search/blob/master/CODE_OF_CONDUCT.md). diff --git a/bin/console b/bin/console index 6e6d590..5b9e60d 100755 --- a/bin/console +++ b/bin/console @@ -1,7 +1,7 @@ #!/usr/bin/env ruby require "bundler/setup" -require "syntax_error_search" +require "syntax_search" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. diff --git a/exe/syntax_search b/exe/syntax_search index 1e37d0f..d506a9b 100755 --- a/exe/syntax_search +++ b/exe/syntax_search @@ -2,7 +2,7 @@ require 'pathname' require "optparse" -require_relative "../lib/syntax_error_search.rb" +require_relative "../lib/syntax_search.rb" options = {} options[:terminal] = true diff --git a/lib/syntax_error_search.rb b/lib/syntax_search.rb similarity index 90% rename from lib/syntax_error_search.rb rename to lib/syntax_search.rb index 974a886..028f171 100644 --- a/lib/syntax_error_search.rb +++ b/lib/syntax_search.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "syntax_error_search/version" +require_relative "syntax_search/version" require 'parser/current' require 'tmpdir' @@ -124,8 +124,8 @@ def self.valid?(source) end end -require_relative "syntax_error_search/code_line" -require_relative "syntax_error_search/code_block" -require_relative "syntax_error_search/code_frontier" -require_relative "syntax_error_search/code_search" -require_relative "syntax_error_search/display_invalid_blocks" +require_relative "syntax_search/code_line" +require_relative "syntax_search/code_block" +require_relative "syntax_search/code_frontier" +require_relative "syntax_search/code_search" +require_relative "syntax_search/display_invalid_blocks" diff --git a/lib/syntax_error_search/auto.rb b/lib/syntax_search/auto.rb similarity index 96% rename from lib/syntax_error_search/auto.rb rename to lib/syntax_search/auto.rb index 7ba0137..ad47389 100644 --- a/lib/syntax_error_search/auto.rb +++ b/lib/syntax_search/auto.rb @@ -1,4 +1,4 @@ -require_relative "../syntax_error_search" +require_relative "../syntax_search" # Monkey patch kernel to ensure that all `require` calls call the same # method diff --git a/lib/syntax_error_search/code_block.rb b/lib/syntax_search/code_block.rb similarity index 100% rename from lib/syntax_error_search/code_block.rb rename to lib/syntax_search/code_block.rb diff --git a/lib/syntax_error_search/code_frontier.rb b/lib/syntax_search/code_frontier.rb similarity index 100% rename from lib/syntax_error_search/code_frontier.rb rename to lib/syntax_search/code_frontier.rb diff --git a/lib/syntax_error_search/code_line.rb b/lib/syntax_search/code_line.rb similarity index 100% rename from lib/syntax_error_search/code_line.rb rename to lib/syntax_search/code_line.rb diff --git a/lib/syntax_error_search/code_search.rb b/lib/syntax_search/code_search.rb similarity index 100% rename from lib/syntax_error_search/code_search.rb rename to lib/syntax_search/code_search.rb diff --git a/lib/syntax_error_search/display_invalid_blocks.rb b/lib/syntax_search/display_invalid_blocks.rb similarity index 100% rename from lib/syntax_error_search/display_invalid_blocks.rb rename to lib/syntax_search/display_invalid_blocks.rb diff --git a/lib/syntax_error_search/fyi.rb b/lib/syntax_search/fyi.rb similarity index 79% rename from lib/syntax_error_search/fyi.rb rename to lib/syntax_search/fyi.rb index 609d246..0bfc17c 100644 --- a/lib/syntax_error_search/fyi.rb +++ b/lib/syntax_search/fyi.rb @@ -1,4 +1,4 @@ -require_relative "../syntax_error_search" +require_relative "../syntax_search" require_relative "auto.rb" diff --git a/lib/syntax_error_search/version.rb b/lib/syntax_search/version.rb similarity index 100% rename from lib/syntax_error_search/version.rb rename to lib/syntax_search/version.rb diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 67cfef4..d9cf0bd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "bundler/setup" -require "syntax_error_search" +require "syntax_search" RSpec.configure do |config| # Enable flags like --only-failures and --next-failure diff --git a/spec/unit/exe_spec.rb b/spec/unit/exe_spec.rb index b0a1f3b..2861d43 100644 --- a/spec/unit/exe_spec.rb +++ b/spec/unit/exe_spec.rb @@ -12,7 +12,6 @@ def exe(cmd) run!("#{exe_path} #{cmd}") end - it "parses valid code" do ruby_file = exe_path out = exe(ruby_file) diff --git a/spec/syntax_error_search_spec.rb b/spec/unit/syntax_search_spec.rb similarity index 95% rename from spec/syntax_error_search_spec.rb rename to spec/unit/syntax_search_spec.rb index 3b82b9d..81141ce 100644 --- a/spec/syntax_error_search_spec.rb +++ b/spec/unit/syntax_search_spec.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative "../spec_helper.rb" + module SyntaxErrorSearch RSpec.describe SyntaxErrorSearch do it "has a version number" do @@ -7,7 +9,7 @@ module SyntaxErrorSearch end def run_ruby(script) - `ruby -I#{lib_dir} -rsyntax_error_search/auto #{script} 2>&1` + `ruby -I#{lib_dir} -rsyntax_search/auto #{script} 2>&1` end it "detects require error and adds a message" do @@ -44,7 +46,7 @@ def run_ruby(script) dir = Pathname(dir) gemfile = dir.join("Gemfile") gemfile.write(<<~EOM) - gem "syntax_search", path: "#{root_dir}", require: "syntax_error_search/auto" + gem "syntax_search", path: "#{root_dir}", require: "syntax_search/auto" EOM run!("BUNDLE_GEMFILE=#{gemfile} bundle install --local") script = dir.join("script.rb") @@ -78,13 +80,12 @@ def run_ruby(script) end end - it "detects require error and adds a message when executed via bundler auto" do Dir.mktmpdir do |dir| dir = Pathname(dir) gemfile = dir.join("Gemfile") gemfile.write(<<~EOM) - gem "syntax_search", path: "#{root_dir}", require: "syntax_error_search/fyi" + gem "syntax_search", path: "#{root_dir}", require: "syntax_search/fyi" EOM run!("BUNDLE_GEMFILE=#{gemfile} bundle install --local") script = dir.join("script.rb") diff --git a/syntax_error_search.gemspec b/syntax_search.gemspec similarity index 87% rename from syntax_error_search.gemspec rename to syntax_search.gemspec index 4443803..cd5d11d 100644 --- a/syntax_error_search.gemspec +++ b/syntax_search.gemspec @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'lib/syntax_error_search/version' +require_relative 'lib/syntax_search/version' Gem::Specification.new do |spec| spec.name = "syntax_search" @@ -10,12 +10,12 @@ Gem::Specification.new do |spec| spec.summary = %q{Find syntax errors in your source in a snap} spec.description = %q{When you get an "unexpected end" in your syntax this gem helps you find it} - spec.homepage = "https://github.com/zombocom/syntax_error_search.git" + spec.homepage = "https://github.com/zombocom/syntax_search.git" spec.license = "MIT" spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0") spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "https://github.com/zombocom/syntax_error_search.git" + spec.metadata["source_code_uri"] = "https://github.com/zombocom/syntax_search.git" # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. From 16d74950e809faccbe219826655e29ae50aa18fa Mon Sep 17 00:00:00 2001 From: schneems Date: Tue, 10 Nov 2020 14:07:37 -0600 Subject: [PATCH 5/8] Fix test description --- spec/unit/syntax_search_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/syntax_search_spec.rb b/spec/unit/syntax_search_spec.rb index 81141ce..569ae3d 100644 --- a/spec/unit/syntax_search_spec.rb +++ b/spec/unit/syntax_search_spec.rb @@ -80,7 +80,7 @@ def run_ruby(script) end end - it "detects require error and adds a message when executed via bundler auto" do + it "detects require error and adds a message when executed via bundler fyi" do Dir.mktmpdir do |dir| dir = Pathname(dir) gemfile = dir.join("Gemfile") From 7276c90940597185641123d30736f0d299dfa44b Mon Sep 17 00:00:00 2001 From: schneems Date: Tue, 10 Nov 2020 14:09:53 -0600 Subject: [PATCH 6/8] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d11e4b..2ab6f2d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ What happened? Likely you forgot a `def`, `do`, or maybe you deleted some code a What if I told you, that there was a library that helped find your missing `def`s and missing `do`s. What if instead of searching through hundreds of lines of source for the cause of your syntax error, there was a way to highlight just code in the file that contained syntax errors. ``` -$ syntax_search path/to/file.rb +$ syntax_search SyntaxErrorSearch: A syntax error was detected From 7a53461deee993ff1aea9786867d3d5f3c3b26b5 Mon Sep 17 00:00:00 2001 From: schneems Date: Tue, 10 Nov 2020 14:10:06 -0600 Subject: [PATCH 7/8] Put FYI above search just-in-case --- lib/syntax_search.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/syntax_search.rb b/lib/syntax_search.rb index 028f171..0360bed 100644 --- a/lib/syntax_search.rb +++ b/lib/syntax_search.rb @@ -16,6 +16,8 @@ def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT) filename = e.message.split(":").first + $stderr.puts "Run `$ syntax_search #{filename}` for more options\n" + if search_source_on_error self.call( source: Pathname(filename).read, @@ -23,7 +25,9 @@ def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT) terminal: true ) end - $stderr.puts "Run `$ syntax_search #{filename}` for more options\n\n" + + $stderr.puts "" + $stderr.puts "" raise e end From f5b074665b13e1a31b172f54aa1cf1079b81e88d Mon Sep 17 00:00:00 2001 From: schneems Date: Tue, 10 Nov 2020 14:19:19 -0600 Subject: [PATCH 8/8] Fix CircleCI --- .circleci/config.yml | 12 ++--- lib/syntax_search.rb | 3 +- spec/unit/exe_spec.rb | 2 +- spec/unit/syntax_search_spec.rb | 81 ++++++--------------------------- 4 files changed, 21 insertions(+), 77 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4b0e6a7..4f62b8c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,31 +10,25 @@ references: jobs: "ruby-2-5": docker: - - image: 'cimg/base:stable' + - image: circleci/ruby:2.5 steps: - checkout - - ruby/install: - version: "2.5" - ruby/install-deps - <<: *unit "ruby-2-6": docker: - - image: 'cimg/base:stable' + - image: circleci/ruby:2.6 steps: - checkout - - ruby/install: - version: "2.6" - ruby/install-deps - <<: *unit "ruby-2-7": docker: - - image: 'cimg/base:stable' + - image: circleci/ruby:2.7 steps: - checkout - - ruby/install: - version: "2.7" - ruby/install-deps - <<: *unit diff --git a/lib/syntax_search.rb b/lib/syntax_search.rb index 0360bed..2513716 100644 --- a/lib/syntax_search.rb +++ b/lib/syntax_search.rb @@ -12,10 +12,11 @@ class Error < StandardError; end SEARCH_SOURCE_ON_ERROR_DEFAULT = true def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT) - raise e if !e.message.include?("unexpected `end'") + raise e if !e.message.include?("expecting end-of-input") filename = e.message.split(":").first + $stderr.sync = true $stderr.puts "Run `$ syntax_search #{filename}` for more options\n" if search_source_on_error diff --git a/spec/unit/exe_spec.rb b/spec/unit/exe_spec.rb index 2861d43..fcb47d2 100644 --- a/spec/unit/exe_spec.rb +++ b/spec/unit/exe_spec.rb @@ -15,7 +15,7 @@ def exe(cmd) it "parses valid code" do ruby_file = exe_path out = exe(ruby_file) - expect(out.strip).to eq("Syntax OK") + expect(out.strip).to include("Syntax OK") end it "parses invalid code" do diff --git a/spec/unit/syntax_search_spec.rb b/spec/unit/syntax_search_spec.rb index 569ae3d..97e6e98 100644 --- a/spec/unit/syntax_search_spec.rb +++ b/spec/unit/syntax_search_spec.rb @@ -8,11 +8,7 @@ module SyntaxErrorSearch expect(SyntaxErrorSearch::VERSION).not_to be nil end - def run_ruby(script) - `ruby -I#{lib_dir} -rsyntax_search/auto #{script} 2>&1` - end - - it "detects require error and adds a message" do + it "detects require error and adds a message with auto mode" do Dir.mktmpdir do |dir| @tmpdir = Pathname(dir) @script = @tmpdir.join("script.rb") @@ -34,23 +30,19 @@ def run_ruby(script) require_relative "./script.rb" EOM - out = run_ruby(require_rb) - # puts out + out = `ruby -I#{lib_dir} -rsyntax_search/auto #{require_rb} 2>&1` + expect(out).to include("This code has an unmatched") expect(out).to include("Run `$ syntax_search") + expect($?.success?).to be_falsey end end - it "detects require error and adds a message when executed via bundler auto" do + it "detects require error and adds a message with fyi mode" do Dir.mktmpdir do |dir| - dir = Pathname(dir) - gemfile = dir.join("Gemfile") - gemfile.write(<<~EOM) - gem "syntax_search", path: "#{root_dir}", require: "syntax_search/auto" - EOM - run!("BUNDLE_GEMFILE=#{gemfile} bundle install --local") - script = dir.join("script.rb") - script.write <<~EOM + @tmpdir = Pathname(dir) + @script = @tmpdir.join("script.rb") + @script.write <<~EOM describe "things" do it "blerg" do end @@ -63,59 +55,16 @@ def run_ruby(script) end EOM - Bundler.with_original_env do - require_rb = dir.join("require.rb") - require_rb.write <<~EOM - Bundler.require - - require_relative "./script.rb" - EOM - - out = `BUNDLE_GEMFILE=#{gemfile} bundle exec ruby #{require_rb} 2>&1` - - expect($?.success?).to be_falsey - expect(out).to include("This code has an unmatched") - expect(out).to include("Run `$ syntax_search") - end - end - end - - it "detects require error and adds a message when executed via bundler fyi" do - Dir.mktmpdir do |dir| - dir = Pathname(dir) - gemfile = dir.join("Gemfile") - gemfile.write(<<~EOM) - gem "syntax_search", path: "#{root_dir}", require: "syntax_search/fyi" - EOM - run!("BUNDLE_GEMFILE=#{gemfile} bundle install --local") - script = dir.join("script.rb") - script.write <<~EOM - describe "things" do - it "blerg" do - end - - it "flerg" - end - - it "zlerg" do - end - end + require_rb = @tmpdir.join("require.rb") + require_rb.write <<~EOM + require_relative "./script.rb" EOM - Bundler.with_original_env do - require_rb = dir.join("require.rb") - require_rb.write <<~EOM - Bundler.require + out = `ruby -I#{lib_dir} -rsyntax_search/fyi #{require_rb} 2>&1` - require_relative "./script.rb" - EOM - - out = `BUNDLE_GEMFILE=#{gemfile} bundle exec ruby #{require_rb} 2>&1` - - expect($?.success?).to be_falsey - expect(out).to_not include("This code has an unmatched") - expect(out).to include("Run `$ syntax_search") - end + expect(out).to_not include("This code has an unmatched") + expect(out).to include("Run `$ syntax_search") + expect($?.success?).to be_falsey end end end