From 244d95aca742339a186bb61e7848863d680a1ebb Mon Sep 17 00:00:00 2001 From: Mauro Otonelli Date: Sat, 16 Oct 2021 16:33:45 -0300 Subject: [PATCH 1/7] Support naked braces/brackets/parens, invert labels on banner Added support for some cases where the label is not shown, specifically when using "naked" braces/brackets, see https://github.com/zombocom/dead_end/issues/84 Extracted a `Banner` class, based on https://github.com/zombocom/dead_end/issues/85, and corrected the labels to show the right matching/non-matching character. --- lib/dead_end/banner.rb | 71 ++++++++++++++ lib/dead_end/display_invalid_blocks.rb | 39 +------- lib/dead_end/who_dis_syntax_error.rb | 3 + spec/unit/banner_spec.rb | 120 +++++++++++++++++++++++ spec/unit/display_invalid_blocks_spec.rb | 81 --------------- spec/unit/who_dis_syntax_error_spec.rb | 50 +++++++--- 6 files changed, 230 insertions(+), 134 deletions(-) create mode 100644 lib/dead_end/banner.rb create mode 100644 spec/unit/banner_spec.rb diff --git a/lib/dead_end/banner.rb b/lib/dead_end/banner.rb new file mode 100644 index 0000000..7862647 --- /dev/null +++ b/lib/dead_end/banner.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module DeadEnd + class Banner + CHARACTERS = { :"}" => :"{", :"]" => :"[", :")" => :"(" } + ALL_CHARACTERS = CHARACTERS.keys + CHARACTERS.values + 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 :unexpected_syntax + case unmatched_symbol + when *ALL_CHARACTERS + <<~EOM + DeadEnd: Unmatched `#{unmatched_symbol}` character detected + + It appears a `#{missing_character}` is missing. + EOM + else + "DeadEnd: Unmatched `#{missing_character}` character detected" + end + 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 *ALL_CHARACTERS + <<~EOM + DeadEnd: Unmatched `#{missing_character}` character detected + + It appears a `#{unmatched_symbol}` is missing. + EOM + else + "DeadEnd: Unmatched `#{missing_character}` detected" + end + end + end + + private def missing_character + CHARACTERS[unmatched_symbol] || CHARACTERS.key(unmatched_symbol) || :unknown + end + + private def unmatched_symbol + invalid_obj.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..332b194 100644 --- a/lib/dead_end/who_dis_syntax_error.rb +++ b/lib/dead_end/who_dis_syntax_error.rb @@ -61,6 +61,9 @@ def on_parse_error(msg) when /unexpected .* expecting ['`]?(?[^']*)/ @unmatched_symbol = $1.to_sym if $1 @error_symbol = :unmatched_syntax + when /unexpected '(?.*)'/ + @unmatched_symbol = $1.to_sym if $1 + @error_symbol = :unexpected_syntax when /unexpected `end'/, # Ruby 2.7 and 3.0 /unexpected end/, # Ruby 2.6 /unexpected keyword_end/i # Ruby 2.5 diff --git a/spec/unit/banner_spec.rb b/spec/unit/banner_spec.rb new file mode 100644 index 0000000..5352371 --- /dev/null +++ b/spec/unit/banner_spec.rb @@ -0,0 +1,120 @@ +# 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 + 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..b62e76a 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).error_symbol + ).to eq(:unmatched_syntax) + + expect( + DeadEnd.invalid_type(source).unmatched_symbol + ).to eq(:"]") + end - expect( - DeadEnd.invalid_type(source).unmatched_symbol - ).to eq(:"]") + it "with naked bracket" do + source = <<~EOM + def initialize + ] + end + EOM + + expect( + DeadEnd.invalid_type(source).error_symbol + ).to eq(:unexpected_syntax) + + expect( + DeadEnd.invalid_type(source).unmatched_symbol + ).to eq(:"]") + end end end end From 58e5ef21dbc7d43eeeeac475be7c3f8bc549857f Mon Sep 17 00:00:00 2001 From: Mauro Otonelli Date: Sat, 16 Oct 2021 16:45:02 -0300 Subject: [PATCH 2/7] Fix linting issues --- lib/dead_end/banner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dead_end/banner.rb b/lib/dead_end/banner.rb index 7862647..6342eb0 100644 --- a/lib/dead_end/banner.rb +++ b/lib/dead_end/banner.rb @@ -2,7 +2,7 @@ module DeadEnd class Banner - CHARACTERS = { :"}" => :"{", :"]" => :"[", :")" => :"(" } + CHARACTERS = {"}": :"{", "]": :"[", ")": :"("} ALL_CHARACTERS = CHARACTERS.keys + CHARACTERS.values attr_reader :invalid_obj From 78303df9daeb55c85ce4682f13055e48db205453 Mon Sep 17 00:00:00 2001 From: Mauro Otonelli Date: Sat, 16 Oct 2021 16:45:38 -0300 Subject: [PATCH 3/7] Added changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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) From 17b415168117f0885f242c48b2f415eccf4cf4b3 Mon Sep 17 00:00:00 2001 From: Mauro Otonelli Date: Sat, 16 Oct 2021 16:58:45 -0300 Subject: [PATCH 4/7] Skip } check for Ruby 2.6 --- spec/unit/banner_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/unit/banner_spec.rb b/spec/unit/banner_spec.rb index 5352371..86f86a5 100644 --- a/spec/unit/banner_spec.rb +++ b/spec/unit/banner_spec.rb @@ -28,6 +28,8 @@ class Cat 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 = } From a46d8652d7bd9e77c6dccfb1140a187d4a8b3581 Mon Sep 17 00:00:00 2001 From: Mauro Otonelli Date: Tue, 19 Oct 2021 10:18:19 -0300 Subject: [PATCH 5/7] Move symbol invert logic to whodis, re-unified unmatched syntax/symbol check --- lib/dead_end/banner.rb | 29 +++++++------------------- lib/dead_end/who_dis_syntax_error.rb | 8 +++++-- spec/unit/who_dis_syntax_error_spec.rb | 4 ++-- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/lib/dead_end/banner.rb b/lib/dead_end/banner.rb index 6342eb0..bcf2422 100644 --- a/lib/dead_end/banner.rb +++ b/lib/dead_end/banner.rb @@ -2,8 +2,6 @@ module DeadEnd class Banner - CHARACTERS = {"}": :"{", "]": :"[", ")": :"("} - ALL_CHARACTERS = CHARACTERS.keys + CHARACTERS.values attr_reader :invalid_obj def initialize(invalid_obj:) @@ -19,17 +17,6 @@ def call This code has a missing `end`. Ensure that all syntax keywords (`def`, `do`, etc.) have a matching `end`. EOM - when :unexpected_syntax - case unmatched_symbol - when *ALL_CHARACTERS - <<~EOM - DeadEnd: Unmatched `#{unmatched_symbol}` character detected - - It appears a `#{missing_character}` is missing. - EOM - else - "DeadEnd: Unmatched `#{missing_character}` character detected" - end when :unmatched_syntax case unmatched_symbol when :end @@ -48,24 +35,24 @@ def call `do |x` should be `do |x|` EOM - when *ALL_CHARACTERS + when *WhoDisSyntaxError::CHARACTERS.keys <<~EOM - DeadEnd: Unmatched `#{missing_character}` character detected + DeadEnd: Unmatched `#{unmatched_symbol}` character detected - It appears a `#{unmatched_symbol}` is missing. + It appears a `#{missing_character}` is missing. EOM else - "DeadEnd: Unmatched `#{missing_character}` detected" + "DeadEnd: Unmatched `#{unmatched_symbol}` detected" end end end - private def missing_character - CHARACTERS[unmatched_symbol] || CHARACTERS.key(unmatched_symbol) || :unknown - 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/who_dis_syntax_error.rb b/lib/dead_end/who_dis_syntax_error.rb index 332b194..64cfff3 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,11 +60,14 @@ def on_parse_error(msg) @unmatched_symbol = :end @error_symbol = :unmatched_syntax when /unexpected .* expecting ['`]?(?[^']*)/ - @unmatched_symbol = $1.to_sym if $1 + if $1 + character = $1.to_sym + @unmatched_symbol = CHARACTERS[character] || CHARACTERS.key(character) || character + end @error_symbol = :unmatched_syntax when /unexpected '(?.*)'/ @unmatched_symbol = $1.to_sym if $1 - @error_symbol = :unexpected_syntax + @error_symbol = :unmatched_syntax when /unexpected `end'/, # Ruby 2.7 and 3.0 /unexpected end/, # Ruby 2.6 /unexpected keyword_end/i # Ruby 2.5 diff --git a/spec/unit/who_dis_syntax_error_spec.rb b/spec/unit/who_dis_syntax_error_spec.rb index b62e76a..cdfb41c 100644 --- a/spec/unit/who_dis_syntax_error_spec.rb +++ b/spec/unit/who_dis_syntax_error_spec.rb @@ -103,7 +103,7 @@ def call expect( DeadEnd.invalid_type(source).unmatched_symbol - ).to eq(:"]") + ).to eq(:"[") end it "with naked bracket" do @@ -115,7 +115,7 @@ def initialize expect( DeadEnd.invalid_type(source).error_symbol - ).to eq(:unexpected_syntax) + ).to eq(:unmatched_syntax) expect( DeadEnd.invalid_type(source).unmatched_symbol From 04c94ffecf43a0f31d87ac5d143d189a44c76959 Mon Sep 17 00:00:00 2001 From: Mauro Otonelli Date: Tue, 19 Oct 2021 10:30:18 -0300 Subject: [PATCH 6/7] Get rid of key check, we need only check the hash values now --- lib/dead_end/who_dis_syntax_error.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dead_end/who_dis_syntax_error.rb b/lib/dead_end/who_dis_syntax_error.rb index 64cfff3..379cb44 100644 --- a/lib/dead_end/who_dis_syntax_error.rb +++ b/lib/dead_end/who_dis_syntax_error.rb @@ -62,7 +62,7 @@ def on_parse_error(msg) when /unexpected .* expecting ['`]?(?[^']*)/ if $1 character = $1.to_sym - @unmatched_symbol = CHARACTERS[character] || CHARACTERS.key(character) || character + @unmatched_symbol = CHARACTERS[character] || character end @error_symbol = :unmatched_syntax when /unexpected '(?.*)'/ From d86613fa8a7fe21467b6a5841f48957435a3eee3 Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Tue, 19 Oct 2021 10:07:57 -0500 Subject: [PATCH 7/7] Update lib/dead_end/banner.rb Co-authored-by: Mauro Otonelli --- lib/dead_end/banner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dead_end/banner.rb b/lib/dead_end/banner.rb index bcf2422..b04ad35 100644 --- a/lib/dead_end/banner.rb +++ b/lib/dead_end/banner.rb @@ -39,7 +39,7 @@ def call <<~EOM DeadEnd: Unmatched `#{unmatched_symbol}` character detected - It appears a `#{missing_character}` is missing. + It appears a `#{missing_character}` might be missing. EOM else "DeadEnd: Unmatched `#{unmatched_symbol}` detected"