diff --git a/.gitignore b/.gitignore index b04a8c8..98af4ab 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ # rspec failure tracking .rspec_status +.DS_Store + diff --git a/Gemfile b/Gemfile index cf46683..bb44deb 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source "https://rubygems.org" # Specify your gem's dependencies in syntax_error_search.gemspec diff --git a/Rakefile b/Rakefile index b7e9ed5..ee617bb 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "bundler/gem_tasks" require "rspec/core/rake_task" diff --git a/lib/syntax_error_search.rb b/lib/syntax_error_search.rb index 943de76..6c7a7a3 100644 --- a/lib/syntax_error_search.rb +++ b/lib/syntax_error_search.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "syntax_error_search/version" require 'parser/current' @@ -10,7 +12,7 @@ class Error < StandardError; end # Used for counting spaces module SpaceCount def self.indent(string) - string.split(/\w/).first&.length || 0 + string.split(/\S/).first&.length || 0 end end diff --git a/lib/syntax_error_search/code_block.rb b/lib/syntax_error_search/code_block.rb index 98c1432..86386e1 100644 --- a/lib/syntax_error_search/code_block.rb +++ b/lib/syntax_error_search/code_block.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SyntaxErrorSearch # Multiple lines form a singular CodeBlock # diff --git a/lib/syntax_error_search/code_frontier.rb b/lib/syntax_error_search/code_frontier.rb index 1a23301..6921bfe 100644 --- a/lib/syntax_error_search/code_frontier.rb +++ b/lib/syntax_error_search/code_frontier.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SyntaxErrorSearch # This class is responsible for generating, storing, and sorting code blocks # @@ -185,10 +187,13 @@ def next_block indent = @indent_hash.keys.sort.last lines = @indent_hash[indent].first - CodeBlock.new( + block = CodeBlock.new( lines: lines, code_lines: @code_lines ).expand_until_neighbors + + register(block) + block end # This method is responsible for determining if a new code @@ -201,16 +206,21 @@ def generate_new_block? @frontier.last.current_indent <= @indent_hash.keys.sort.last end + def register(block) + block.lines.each do |line| + @indent_hash[line.indent]&.delete(line) + end + @indent_hash.select! {|k, v| !v.empty?} + self + end + # Add a block to the frontier # # This method ensures the frontier always remains sorted (in indentation order) # and that each code block's lines are removed from the indentation hash so we # don't re-evaluate the same line multiple times. def <<(block) - block.lines.each do |line| - @indent_hash[line.indent]&.delete(line) - end - @indent_hash.select! {|k, v| !v.empty?} + register(block) @frontier << block @frontier.sort! diff --git a/lib/syntax_error_search/code_line.rb b/lib/syntax_error_search/code_line.rb index d090cf8..8b6fa5e 100644 --- a/lib/syntax_error_search/code_line.rb +++ b/lib/syntax_error_search/code_line.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SyntaxErrorSearch # Represents a single line of code of a given source file # diff --git a/lib/syntax_error_search/code_search.rb b/lib/syntax_error_search/code_search.rb index 2f780f7..23ce509 100644 --- a/lib/syntax_error_search/code_search.rb +++ b/lib/syntax_error_search/code_search.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SyntaxErrorSearch # Searches code for a syntax error # @@ -24,28 +26,66 @@ module SyntaxErrorSearch # class CodeSearch private; attr_reader :frontier; public - public; attr_reader :invalid_blocks + public; attr_reader :invalid_blocks, :record_dir - def initialize(string) + 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) + end @code_lines = string.lines.map.with_index do |line, i| CodeLine.new(line: line, index: i) end @frontier = CodeFrontier.new(code_lines: @code_lines) @invalid_blocks = [] + @name_tick = Hash.new {|hash, k| hash[k] = 0 } + @tick = 0 end - def call - until frontier.holds_all_syntax_errors? - frontier << frontier.next_block if frontier.next_block? + 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| + display = DisplayInvalidBlocks.new( + blocks: block, + terminal: false + ) + f.write(display.indent display.code_with_lines) + end + end + + def expand_frontier + return if !frontier.next_block? + block = frontier.next_block + record(block: block, name: "add") + if block.valid? + block.lines.each(&:mark_invisible) + return expand_frontier + else + frontier << block + end + block + end - block = frontier.pop + def search + block = frontier.pop - if block.valid? - block.lines.each(&:mark_invisible) - else - block.expand_until_neighbors - frontier << block - end + block.expand_until_next_boundry + record(block: block, name: "expand") + if block.valid? + block.lines.each(&:mark_invisible) + else + frontier << block + end + 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 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 e25b157..c329161 100644 --- a/lib/syntax_error_search/display_invalid_blocks.rb +++ b/lib/syntax_error_search/display_invalid_blocks.rb @@ -1,17 +1,20 @@ +# frozen_string_literal: true + module SyntaxErrorSearch # Used for formatting invalid blocks class DisplayInvalidBlocks attr_reader :filename - def initialize(block_array, io: $stderr, filename: nil) + def initialize(blocks:, io: $stderr, filename: nil, terminal: false) + @terminal = terminal @filename = filename @io = io - @blocks = block_array + @blocks = Array(blocks) @lines = @blocks.map(&:lines).flatten - @digit_count = @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} + @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true } end def call @@ -28,35 +31,53 @@ def call @io.puts <<~EOM simplified: - #{code_with_filename(indent: 2)} + #{indent(code_block)} EOM + self end + def indent(string, with: " ") + string.each_line.map {|l| with + l }.join + end - def code_with_filename(indent: 0) + def code_block string = String.new("") string << "```\n" # string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename string << code_with_lines string << "```\n" + string + end + + def terminal_end + "\e[0m" + end - string.each_line.map {|l| " " * indent + l }.join + def terminal_highlight + "\e[1;3m" # Bold, italics end def code_with_lines @code_lines.map do |line| next if line.hidden? + string = String.new("") + if @invalid_line_hash[line] + string << "❯ " + else + string << " " + end + number = line.line_number.to_s.rjust(@digit_count) + string << number.to_s if line.empty? - "#{number.to_s}#{line}" + string << line.to_s else - string = String.new - string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics - string << "#{number.to_s} " + string << " " + string << terminal_highlight if @terminal && @invalid_line_hash[line] # Bold, italics string << line.to_s - string << "\e[0m" - string + string << terminal_end if @terminal end + string end.join end end diff --git a/lib/syntax_error_search/version.rb b/lib/syntax_error_search/version.rb index dfe477a..18b6e47 100644 --- a/lib/syntax_error_search/version.rb +++ b/lib/syntax_error_search/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SyntaxErrorSearch VERSION = "0.1.0" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 434a7cc..26ae3fe 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "bundler/setup" require "syntax_error_search" diff --git a/spec/syntax_error_search_spec.rb b/spec/syntax_error_search_spec.rb index e4f29bc..64bb37d 100644 --- a/spec/syntax_error_search_spec.rb +++ b/spec/syntax_error_search_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SyntaxErrorSearch RSpec.describe SyntaxErrorSearch do it "has a version number" do diff --git a/spec/unit/code_block_spec.rb b/spec/unit/code_block_spec.rb index 7b0bf51..7fbbcc3 100644 --- a/spec/unit/code_block_spec.rb +++ b/spec/unit/code_block_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper.rb" module SyntaxErrorSearch diff --git a/spec/unit/code_frontier_spec.rb b/spec/unit/code_frontier_spec.rb index 1f963a2..292308c 100644 --- a/spec/unit/code_frontier_spec.rb +++ b/spec/unit/code_frontier_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper.rb" module SyntaxErrorSearch diff --git a/spec/unit/code_line_spec.rb b/spec/unit/code_line_spec.rb index 4dec437..5a82ed3 100644 --- a/spec/unit/code_line_spec.rb +++ b/spec/unit/code_line_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper.rb" module SyntaxErrorSearch diff --git a/spec/unit/code_search_spec.rb b/spec/unit/code_search_spec.rb index 31796c0..ce1c8f6 100644 --- a/spec/unit/code_search_spec.rb +++ b/spec/unit/code_search_spec.rb @@ -1,8 +1,58 @@ +# frozen_string_literal: true + require_relative "../spec_helper.rb" module SyntaxErrorSearch RSpec.describe CodeSearch do + it "recording" do + Dir.mktmpdir do |dir| + dir = Pathname(dir) + search = CodeSearch.new(<<~EOM, record_dir: dir) + class OH + def hello + def hai + end + end + EOM + search.call + + 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 + ❯ 3 def hai + ❯ 4 end + 5 end + EOM + end + end + it "def with missing end" do + search = CodeSearch.new(<<~EOM) + class OH + def hello + + def hai + puts "lol" + end + end + EOM + search.call + + expect(search.invalid_blocks.join.strip).to eq("def hello") + + search = CodeSearch.new(<<~EOM) + class OH + def hello + + def hai + end + end + EOM + search.call + + expect(search.invalid_blocks.join.strip).to eq("def hello") + search = CodeSearch.new(<<~EOM) class OH def hello @@ -23,21 +73,46 @@ def hai # 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 + + # puts block.before_line.to_s.inspect + # puts block.before_line.to_s.split(/\S/).inspect + # puts block.before_line.indent + + # puts block.after_line.to_s.inspect + # 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 it "this project" do - skip("Lol the results are really bad on this one") - search = CodeSearch.new(fixtures_dir.join("this_project_extra_def.rb.txt").read) + 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, io: io, filename: "fake/spec/lol.rb") + display = DisplayInvalidBlocks.new( + blocks: blocks, + io: io, + ) display.call - puts io.string + # puts io.string - expect(display.code_with_lines.strip_control_codes).to eq(<<~EOM) - 36 def filename + expect(display.code_with_lines.strip_control_codes).to include(<<~EOM) + ❯ 36 def filename EOM end @@ -79,18 +154,18 @@ def hai blocks = search.invalid_blocks io = StringIO.new - display = DisplayInvalidBlocks.new(blocks, io: io, filename: "fake/spec/lol.rb") + 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 + 1 require 'rails_helper' + 2 + 3 RSpec.describe AclassNameHere, type: :worker do + ❯ 4 describe "thing" do + ❯ 16 end # here + ❯ 30 end + 31 end EOM end end @@ -107,8 +182,8 @@ def foo EOM search.call - # Does not include the line with the error Foo.call expect(search.invalid_blocks.join).to eq(<<~EOM) + Foo.call def foo end end @@ -143,8 +218,8 @@ def foo EOM search.call - # Does not include the line with the error Foo.call expect(search.invalid_blocks.join).to eq(<<~EOM) + Foo.call end EOM end @@ -196,11 +271,14 @@ def foo EOM search.call - expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2)) - Foo.call - end - Bar.call + expect(search.invalid_blocks.join).to eq(<<~EOM.indent(0)) + describe "hi" do + Foo.call + end end + + Bar.call + end EOM end diff --git a/spec/unit/display_invalid_blocks_spec.rb b/spec/unit/display_invalid_blocks_spec.rb new file mode 100644 index 0000000..868ada9 --- /dev/null +++ b/spec/unit/display_invalid_blocks_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require_relative "../spec_helper.rb" + +module SyntaxErrorSearch + RSpec.describe DisplayInvalidBlocks do + it "outputs to io when using `call`" do + code_lines = code_line_array(<<~EOM) + class OH + def hello + def hai + end + end + EOM + + io = StringIO.new + block = CodeBlock.new(code_lines: code_lines, lines: code_lines[1]) + display = DisplayInvalidBlocks.new( + blocks: block, + terminal: false, + io: io + ) + display.call + expect(io.string).to include("❯ 2 def hello") + expect(io.string).to include("A syntax error was detected") + end + + it " wraps code with github style codeblocks" do + code_lines = code_line_array(<<~EOM) + class OH + def hello + def hai + end + end + EOM + + block = CodeBlock.new(code_lines: code_lines, lines: code_lines[1]) + display = DisplayInvalidBlocks.new( + blocks: block, + terminal: false + ) + expect(display.code_block).to eq(<<~EOM) + ``` + 1 class OH + ❯ 2 def hello + 3 def hai + 4 end + 5 end + ``` + EOM + end + it "shows terminal characters" do + code_lines = code_line_array(<<~EOM) + class OH + def hello + def hai + end + end + EOM + + block = CodeBlock.new(code_lines: code_lines, lines: code_lines[1]) + display = DisplayInvalidBlocks.new( + blocks: block, + terminal: false + ) + + expect(display.code_with_lines).to eq( + [ + " 1 class OH", + "❯ 2 def hello", + " 3 def hai", + " 4 end", + " 5 end", + "" + ].join($/) + ) + + block = CodeBlock.new(code_lines: code_lines, lines: code_lines[1]) + display = DisplayInvalidBlocks.new( + blocks: block, + terminal: true + ) + + expect(display.code_with_lines).to eq( + [ + " 1 class OH", + ["❯ 2 ", display.terminal_highlight, " def hello"].join, + " 3 def hai", + " 4 end", + " 5 end", + "" + ].join($/ + display.terminal_end) + ) + end + end +end diff --git a/syntax_error_search.gemspec b/syntax_error_search.gemspec index c080c59..786e3a6 100644 --- a/syntax_error_search.gemspec +++ b/syntax_error_search.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'lib/syntax_error_search/version' Gem::Specification.new do |spec|