diff --git a/CHANGELOG.md b/CHANGELOG.md index 92491ae..133f7d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## HEAD (unreleased) +- Support naked braces/brackets/parens, invert labels on banner (https://github.com/zombocom/dead_end/pull/89) - Handle mismatched end when using rescue without begin (https://github.com/zombocom/dead_end/pull/83) - CLI returns non-zero exit code when syntax error is found (https://github.com/zombocom/dead_end/pull/86) - Let -v respond with gem version instead of 'unknown' (https://github.com/zombocom/dead_end/pull/82) diff --git a/lib/dead_end/banner.rb b/lib/dead_end/banner.rb new file mode 100644 index 0000000..b04ad35 --- /dev/null +++ b/lib/dead_end/banner.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module DeadEnd + class Banner + attr_reader :invalid_obj + + def initialize(invalid_obj:) + @invalid_obj = invalid_obj + end + + def call + case invalid_obj.error_symbol + when :missing_end + <<~EOM + DeadEnd: Missing `end` detected + + This code has a missing `end`. Ensure that all + syntax keywords (`def`, `do`, etc.) have a matching `end`. + EOM + when :unmatched_syntax + case unmatched_symbol + when :end + <<~EOM + DeadEnd: 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 + DeadEnd: Unmatched `|` character detected + + Example: + + `do |x` should be `do |x|` + EOM + when *WhoDisSyntaxError::CHARACTERS.keys + <<~EOM + DeadEnd: Unmatched `#{unmatched_symbol}` character detected + + It appears a `#{missing_character}` might be missing. + EOM + else + "DeadEnd: Unmatched `#{unmatched_symbol}` detected" + end + end + end + + private def unmatched_symbol + invalid_obj.unmatched_symbol + end + + private def missing_character + WhoDisSyntaxError::CHARACTERS[unmatched_symbol] + end + end +end diff --git a/lib/dead_end/display_invalid_blocks.rb b/lib/dead_end/display_invalid_blocks.rb index c3056b5..9d22c72 100644 --- a/lib/dead_end/display_invalid_blocks.rb +++ b/lib/dead_end/display_invalid_blocks.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require_relative "banner" require_relative "capture_code_context" require_relative "display_code_with_line_numbers" @@ -54,43 +55,7 @@ def call end def banner - case @invalid_obj.error_symbol - when :missing_end - <<~EOM - DeadEnd: Missing `end` detected - - This code has a missing `end`. Ensure that all - syntax keywords (`def`, `do`, etc.) have a matching `end`. - EOM - when :unmatched_syntax - case @invalid_obj.unmatched_symbol - when :end - <<~EOM - DeadEnd: 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 - DeadEnd: Unmatched `|` character detected - - Example: - - `do |x` should be `do |x|` - EOM - when :"}" - <<~EOM - DeadEnd: Unmatched `}` character detected - - This code has an unmatched `}`. Ensure that opening curly braces are - closed: `{ }`. - EOM - else - "DeadEnd: Unmatched `#{@invalid_obj.unmatched_symbol}` detected" - end - end + Banner.new(invalid_obj: @invalid_obj).call end def indent(string, with: " ") diff --git a/lib/dead_end/who_dis_syntax_error.rb b/lib/dead_end/who_dis_syntax_error.rb index d4770f5..379cb44 100644 --- a/lib/dead_end/who_dis_syntax_error.rb +++ b/lib/dead_end/who_dis_syntax_error.rb @@ -8,6 +8,7 @@ module DeadEnd # puts WhoDisSyntaxError.new("def foo;").call.error_symbol # # => :missing_end class WhoDisSyntaxError < Ripper + CHARACTERS = {"{": :"}", "}": :"{", "[": :"]", "]": :"[", "(": :")", ")": :"("} class Null def error_symbol :missing_end @@ -59,6 +60,12 @@ def on_parse_error(msg) @unmatched_symbol = :end @error_symbol = :unmatched_syntax when /unexpected .* expecting ['`]?(?[^']*)/ + if $1 + character = $1.to_sym + @unmatched_symbol = CHARACTERS[character] || character + end + @error_symbol = :unmatched_syntax + when /unexpected '(?.*)'/ @unmatched_symbol = $1.to_sym if $1 @error_symbol = :unmatched_syntax when /unexpected `end'/, # Ruby 2.7 and 3.0 diff --git a/spec/unit/banner_spec.rb b/spec/unit/banner_spec.rb new file mode 100644 index 0000000..86f86a5 --- /dev/null +++ b/spec/unit/banner_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +module DeadEnd + RSpec.describe Banner do + it "Unmatched | banner" do + source = <<~EOM + Foo.call do | + end + EOM + + invalid_obj = WhoDisSyntaxError.new(source) + banner = Banner.new(invalid_obj: invalid_obj) + expect(banner.call).to include("Unmatched `|` character detected") + end + + it "Unmatched { banner" do + source = <<~EOM + class Cat + lol = { + end + EOM + + invalid_obj = WhoDisSyntaxError.new(source) + banner = Banner.new(invalid_obj: invalid_obj) + expect(banner.call).to include("Unmatched `{` character detected") + end + + it "Unmatched } banner" do + skip("Unsupported ruby version") unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7") + + source = <<~EOM + def foo + lol = } + end + EOM + + invalid_obj = WhoDisSyntaxError.new(source) + banner = Banner.new(invalid_obj: invalid_obj) + expect(banner.call).to include("Unmatched `}` character detected") + end + + it "Unmatched [ banner" do + source = <<~EOM + class Cat + lol = [ + end + EOM + + invalid_obj = WhoDisSyntaxError.new(source) + banner = Banner.new(invalid_obj: invalid_obj) + expect(banner.call).to include("Unmatched `[` character detected") + end + + it "Unmatched ] banner" do + source = <<~EOM + def foo + lol = ] + end + EOM + + invalid_obj = WhoDisSyntaxError.new(source) + banner = Banner.new(invalid_obj: invalid_obj) + expect(banner.call).to include("Unmatched `]` character detected") + end + + it "Unmatched end banner" do + source = <<~EOM + class Cat + end + end + EOM + + invalid_obj = WhoDisSyntaxError.new(source) + banner = Banner.new(invalid_obj: invalid_obj) + expect(banner.call).to include("DeadEnd: Unmatched `end` detected") + end + + it "Unmatched unknown banner" do + source = <<~EOM + class Cat + def meow + 1 * + end + end + EOM + + invalid_obj = WhoDisSyntaxError.new(source) + banner = Banner.new(invalid_obj: invalid_obj) + expect(banner.call).to include("DeadEnd: Unmatched `unknown` detected") + end + + it "missing end banner" do + source = <<~EOM + class Cat + def meow + end + EOM + + invalid_obj = WhoDisSyntaxError.new(source) + banner = Banner.new(invalid_obj: invalid_obj) + expect(banner.call).to include("DeadEnd: Missing `end` detected") + end + + it "naked (closing) parenthesis" do + invalid_obj = WhoDisSyntaxError.new("def initialize; ); end").call + + expect( + Banner.new(invalid_obj: invalid_obj).call + ).to include("Unmatched `)` character detected") + end + + it "naked (opening) parenthesis" do + invalid_obj = WhoDisSyntaxError.new("def initialize; (; end").call + + expect( + Banner.new(invalid_obj: invalid_obj).call + ).to include("Unmatched `(` character detected") + end + end +end diff --git a/spec/unit/display_invalid_blocks_spec.rb b/spec/unit/display_invalid_blocks_spec.rb index 6280c20..64e9674 100644 --- a/spec/unit/display_invalid_blocks_spec.rb +++ b/spec/unit/display_invalid_blocks_spec.rb @@ -4,87 +4,6 @@ module DeadEnd 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 = CleanDocument.new(source: source).call.lines - - 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("DeadEnd: Unmatched `end` detected") - end - - it "Unmatched unknown banner" do - source = <<~EOM - class Cat - def meow - 1 * - 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("DeadEnd: Unmatched `unknown` 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("DeadEnd: Missing `end` detected") - end - it "works with valid code" do syntax_string = <<~EOM class OH diff --git a/spec/unit/who_dis_syntax_error_spec.rb b/spec/unit/who_dis_syntax_error_spec.rb index 732e96d..cdfb41c 100644 --- a/spec/unit/who_dis_syntax_error_spec.rb +++ b/spec/unit/who_dis_syntax_error_spec.rb @@ -82,27 +82,45 @@ class Blerg end end - it "determines the type of syntax error to be an unmatched bracket" do - source = <<~EOM - module Hey - class Foo - def initialize - [1,2,3 - end + context "determines the type of syntax error to be an unmatched bracket" do + it "with missing bracket" do + source = <<~EOM + module Hey + class Foo + def initialize + [1,2,3 + end - def call + def call + end end end - end - EOM + EOM + + expect( + DeadEnd.invalid_type(source).error_symbol + ).to eq(:unmatched_syntax) + + expect( + DeadEnd.invalid_type(source).unmatched_symbol + ).to eq(:"[") + end + + it "with naked bracket" do + source = <<~EOM + def initialize + ] + end + EOM - expect( - DeadEnd.invalid_type(source).error_symbol - ).to eq(:unmatched_syntax) + expect( + DeadEnd.invalid_type(source).error_symbol + ).to eq(:unmatched_syntax) - expect( - DeadEnd.invalid_type(source).unmatched_symbol - ).to eq(:"]") + expect( + DeadEnd.invalid_type(source).unmatched_symbol + ).to eq(:"]") + end end end end