From c66f190f54d8a4829a9d5815a61779df39f6a2a9 Mon Sep 17 00:00:00 2001 From: schneems Date: Thu, 10 Dec 2020 12:50:27 -0600 Subject: [PATCH] Banner for different syntax error types SyntaxSearch works for other missing keywords other than `end` that would otherwise trigger an `unexpected end` error. This commit adds more explicit banners for these cases. https://github.com/zombocom/syntax_search/issues/18 --- CHANGELOG.md | 1 + lib/syntax_search.rb | 4 +- lib/syntax_search/display_invalid_blocks.rb | 66 ++++++++++++++------- lib/syntax_search/who_dis_syntax_error.rb | 42 +++++++++++-- spec/unit/code_search_spec.rb | 38 ++++++++++++ spec/unit/display_invalid_blocks_spec.rb | 63 ++++++++++++++++++++ spec/unit/who_dis_syntax_error_spec.rb | 27 ++++++++- 7 files changed, 212 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c40f54a..7f4ddb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## HEAD (unreleased) +- Error banner now indicates when missing a `|` or `}` in addition to `end` (https://github.com/zombocom/syntax_search/pull/29) - Trailing slashes are now handled (joined) before the code search (https://github.com/zombocom/syntax_search/pull/28) ## 0.2.0 diff --git a/lib/syntax_search.rb b/lib/syntax_search.rb index bc4442d..ed89e23 100644 --- a/lib/syntax_search.rb +++ b/lib/syntax_search.rb @@ -46,7 +46,7 @@ def self.call(source: , filename: , terminal: false, record_dir: nil, timeout: T filename: filename, terminal: terminal, code_lines: search.code_lines, - invalid_type: invalid_type(source), + invalid_obj: invalid_type(source), io: $stderr ).call rescue Timeout::Error @@ -134,7 +134,7 @@ def self.valid?(source) def self.invalid_type(source) - WhoDisSyntaxError.new(source).call.error_symbol + WhoDisSyntaxError.new(source).call end end diff --git a/lib/syntax_search/display_invalid_blocks.rb b/lib/syntax_search/display_invalid_blocks.rb index e1f6766..7ddf2e5 100644 --- a/lib/syntax_search/display_invalid_blocks.rb +++ b/lib/syntax_search/display_invalid_blocks.rb @@ -8,7 +8,7 @@ module SyntaxErrorSearch class DisplayInvalidBlocks attr_reader :filename - def initialize(code_lines: ,blocks:, io: $stderr, filename: nil, terminal: false, invalid_type: :unmatched_end) + def initialize(code_lines: ,blocks:, io: $stderr, filename: nil, terminal: false, invalid_obj: WhoDisSyntaxError::Null.new) @terminal = terminal @filename = filename @io = io @@ -18,7 +18,7 @@ def initialize(code_lines: ,blocks:, io: $stderr, filename: nil, terminal: false @invalid_lines = @blocks.map(&:lines).flatten @code_lines = code_lines - @invalid_type = invalid_type + @invalid_obj = invalid_obj end def call @@ -36,34 +36,56 @@ def call end private def found_invalid_blocks - case @invalid_type - when :missing_end - @io.puts <<~EOM + @io.puts + @io.puts banner + @io.puts + @io.puts("file: #{filename}") if filename + @io.puts <<~EOM + simplified: + + #{indent(code_block)} + EOM + end + def banner + case @invalid_obj.error_symbol + when :missing_end + <<~EOM SyntaxSearch: Missing `end` detected This code has a missing `end`. Ensure that all syntax keywords (`def`, `do`, etc.) have a matching `end`. - - EOM - when :unmatched_end - @io.puts <<~EOM - - SyntaxSearch: Unmatched `end` detected - - This code has an unmatched `end`. Ensure that all `end` lines - in your code have a matching syntax keyword (`def`, `do`, etc.) - and that you don't have any extra `end` lines. - EOM + when :unmatched_syntax + case @invalid_obj.unmatched_symbol + when :end + <<~EOM + SyntaxSearch: Unmatched `end` detected + + This code has an unmatched `end`. Ensure that all `end` lines + in your code have a matching syntax keyword (`def`, `do`, etc.) + and that you don't have any extra `end` lines. + EOM + when :| + <<~EOM + SyntaxSearch: Unmatched `|` character detected + + Example: + + `do |x` should be `do |x|` + EOM + when :"}" + <<~EOM + SyntaxSearch: Unmatched `}` character detected + + This code has an unmatched `}`. Ensure that opening curl braces are + closed: `{ }`. + EOM + else + "SyntaxSearch: Unmatched #{@invalid_obj.unmatched_symbol}` detected" + end end - @io.puts("file: #{filename}") if filename - @io.puts <<~EOM - simplified: - - #{indent(code_block)} - EOM end def indent(string, with: " ") diff --git a/lib/syntax_search/who_dis_syntax_error.rb b/lib/syntax_search/who_dis_syntax_error.rb index 8a86ab3..c5eb227 100644 --- a/lib/syntax_search/who_dis_syntax_error.rb +++ b/lib/syntax_search/who_dis_syntax_error.rb @@ -8,7 +8,30 @@ module SyntaxErrorSearch # puts WhoDisSyntaxError.new("def foo;").call.error_symbol # # => :missing_end class WhoDisSyntaxError < Ripper - attr_reader :error, :run_once, :error_symbol + class Null + def error_symbol; :missing_end; end + def unmatched_symbol; :end ; end + end + attr_reader :error, :run_once + + # Return options: + # - :missing_end + # - :unmatched_syntax + # - :unknown + def error_symbol + call + @error_symbol + end + + # Return options: + # - :end + # - :| + # - :} + # - :unknown + def unmatched_symbol + call + @unmatched_symbol + end def call @run_once ||= begin @@ -20,12 +43,23 @@ def call def on_parse_error(msg) @error = msg + @unmatched_symbol = :unknown + if @error.match?(/unexpected end-of-input/) @error_symbol = :missing_end - elsif @error.match?(/unexpected `end'/) || @error.match?(/expecting end-of-input/) - @error_symbol = :unmatched_end + elsif @error.match?(/expecting end-of-input/) + @error_symbol = :unmatched_syntax + @unmatched_symbol = :end + elsif @error.match?(/unexpected `end'/) || # Ruby 2.7 & 3.0 + @error.match?(/unexpected end,/) || # Ruby 2.6 + @error.match?(/unexpected keyword_end/) # Ruby 2.5 + + @error_symbol = :unmatched_syntax + + match = @error.match(/expecting '(?.*)'/) + @unmatched_symbol = match[:unmatched_symbol].to_sym if match else - @error_symbol = :nope + @error_symbol = :unknown end end end diff --git a/spec/unit/code_search_spec.rb b/spec/unit/code_search_spec.rb index a80eeb6..a5cf0f0 100644 --- a/spec/unit/code_search_spec.rb +++ b/spec/unit/code_search_spec.rb @@ -4,6 +4,44 @@ module SyntaxErrorSearch RSpec.describe CodeSearch do + it "handles mismatched |" do + source = <<~EOM + class Blerg + Foo.call do |a + end # one + + puts lol + class Foo + end # two + end # three + EOM + search = CodeSearch.new(source) + search.call + + expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2)) + Foo.call do |a + end # one + EOM + end + + it "handles mismatched }" do + source = <<~EOM + class Blerg + Foo.call do { + + puts lol + class Foo + end # two + end # three + EOM + search = CodeSearch.new(source) + search.call + + expect(search.invalid_blocks.join).to eq(<<~EOM.indent(2)) + Foo.call do { + EOM + end + it "handles no spaces between blocks" do search = CodeSearch.new(<<~'EOM') require "rails_helper" diff --git a/spec/unit/display_invalid_blocks_spec.rb b/spec/unit/display_invalid_blocks_spec.rb index dbf88ee..82cad0e 100644 --- a/spec/unit/display_invalid_blocks_spec.rb +++ b/spec/unit/display_invalid_blocks_spec.rb @@ -4,6 +4,69 @@ module SyntaxErrorSearch RSpec.describe DisplayInvalidBlocks do + it "Unmatched | banner" do + source = <<~EOM + Foo.call do | + end + EOM + code_lines = code_line_array(source) + + display = DisplayInvalidBlocks.new( + code_lines: code_lines, + blocks: CodeBlock.new(lines: code_lines), + invalid_obj: WhoDisSyntaxError.new(source), + ) + expect(display.banner).to include("Unmatched `|` character detected") + end + + it "Unmatched } banner" do + source = <<~EOM + class Cat + lol = { + end + EOM + code_lines = code_line_array(source) + + display = DisplayInvalidBlocks.new( + code_lines: code_lines, + blocks: CodeBlock.new(lines: code_lines), + invalid_obj: WhoDisSyntaxError.new(source), + ) + expect(display.banner).to include("Unmatched `}` character detected") + end + + it "Unmatched end banner" do + source = <<~EOM + class Cat + end + end + EOM + code_lines = code_line_array(source) + + display = DisplayInvalidBlocks.new( + code_lines: code_lines, + blocks: CodeBlock.new(lines: code_lines), + invalid_obj: WhoDisSyntaxError.new(source), + ) + expect(display.banner).to include("SyntaxSearch: Unmatched `end` detected") + end + + it "missing end banner" do + source = <<~EOM + class Cat + def meow + end + EOM + code_lines = code_line_array(source) + + display = DisplayInvalidBlocks.new( + code_lines: code_lines, + blocks: CodeBlock.new(lines: code_lines), + invalid_obj: WhoDisSyntaxError.new(source), + ) + expect(display.banner).to include("SyntaxSearch: Missing `end` detected") + end + it "captures surrounding context on same indent" do syntax_string = <<~EOM class Blerg diff --git a/spec/unit/who_dis_syntax_error_spec.rb b/spec/unit/who_dis_syntax_error_spec.rb index f1791e8..0280f05 100644 --- a/spec/unit/who_dis_syntax_error_spec.rb +++ b/spec/unit/who_dis_syntax_error_spec.rb @@ -11,7 +11,32 @@ module SyntaxErrorSearch expect( WhoDisSyntaxError.new("def foo; end; end").call.error_symbol - ).to eq(:unmatched_end) + ).to eq(:unmatched_syntax) + + expect( + WhoDisSyntaxError.new("def foo; end; end").call.unmatched_symbol + ).to eq(:end) + end + + it "" do + source = <<~EOM + class Blerg + Foo.call do |a + end # one + + puts lol + class Foo + end # two + end # three + EOM + + expect( + SyntaxErrorSearch.invalid_type(source).error_symbol + ).to eq(:unmatched_syntax) + + expect( + SyntaxErrorSearch.invalid_type(source).unmatched_symbol + ).to eq(:|) end end end