Skip to content

Commit

Permalink
Raise a warning if the input causes an error and partially matches a …
Browse files Browse the repository at this point in the history
…command

As we now proceed to stricter syntax checking for commands, incorrect
command input would be processed as Ruby instead and likely causes
errors. In that case, we should raise a warning to the user.

For example, if an input is `show_source Foo bar`, the user would see
a warning about the possible syntax error for using `show_source` command.

But with this approach, we also need add a condition for the `measure`
command as it's actually used as a method call with block.
  • Loading branch information
st0012 committed Dec 12, 2023
1 parent 98da57e commit c22fe6b
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 15 deletions.
35 changes: 22 additions & 13 deletions lib/irb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,9 @@ def eval_input
rescue Interrupt, Exception => exc
handle_exception(exc)
@context.workspace.local_variable_set(:_, exc)
if statement.warning
warn statement.warning
end
end
end
end
Expand Down Expand Up @@ -1300,22 +1303,28 @@ def each_top_level_statement

def build_statement(code)
code.force_encoding(@context.io.encoding) unless code.frozen?
command_match = COMMAND_REGEXP.match(code.strip)

if command_match
command_or_alias = command_match[:cmd_name]
arg = [command_match[:cmd_arg], command_match[:cmd_flag]].compact.join(' ')
# Transform a non-identifier alias (@, $) or keywords (next, break)
command_name = @context.command_aliases[command_or_alias.to_sym]
command = command_name || command_or_alias
command_class = ExtendCommandBundle.load_command(command)
end

if command_class
Statement::Command.new(code, command, arg, command_class)
possible_command_or_alias = code.split.first

possible_command_name = @context.command_aliases[possible_command_or_alias.to_sym]
possible_command_name = possible_command_name || possible_command_or_alias
command_class = ExtendCommandBundle.load_command(possible_command_name)

command_syntax_match = COMMAND_REGEXP.match(code.strip)

if command_class && command_syntax_match
arg = [command_syntax_match[:cmd_arg], command_syntax_match[:cmd_flag]].compact.join(' ')
Statement::Command.new(code, possible_command_name, arg, command_class)
else
if command_class
warning_msg = <<~MSG
The input `#{code.strip}` was recognised as a Ruby expression, but it matched the name of the `#{possible_command_name}` command.
If you intended to run it as a command, please check if the syntax is correct.
MSG
end

is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables)
Statement::Expression.new(code, is_assignment_expression)
Statement::Expression.new(code, is_assignment_expression, warning: warning_msg)
end
end

Expand Down
8 changes: 6 additions & 2 deletions lib/irb/statement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module IRB
class Statement
attr_reader :code
attr_reader :code, :warning

def is_assignment?
raise NotImplementedError
Expand All @@ -21,9 +21,10 @@ def evaluable_code
end

class Expression < Statement
def initialize(code, is_assignment)
def initialize(code, is_assignment, warning: nil)
@code = code
@is_assignment = is_assignment
@warning = warning
end

def suppresses_echo?
Expand Down Expand Up @@ -66,6 +67,9 @@ def should_be_handled_by_debugger?
end

def evaluable_code
# Because measure command is used as a method, we need to treat it differently
return @code if @command == "measure"

# Hook command-specific transformation to return valid Ruby code
if @command_class.respond_to?(:transform_args)
arg = @command_class.transform_args(@arg)
Expand Down
45 changes: 45 additions & 0 deletions test/irb/test_irb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,51 @@ def test_symbol_aliases_dont_affect_ruby_syntax
assert_include output, "=> \"It's a foo\""
assert_include output, "=> \"It's a bar\""
end

def test_errored_input_that_match_a_command_raises_a_syntax_check_reminder
write_ruby <<~'RUBY'
class Foo
end
binding.irb
RUBY

errored_output = run_ruby_file do
type "show_source Foo bar"
type "exit!"
end

# The input should cause an error as it's evaluated as Ruby code
assert_include errored_output, 'undefined local variable or method `bar\''

# Because it starts with the name of a command, it should also raise a warning
assert_include errored_output,
'The input `show_source Foo bar` was recognised as a Ruby expression, but it matched the name of the `show_source` command.'
assert_include errored_output,
'If you intended to run it as a command, please check if the syntax is correct.'

output = run_ruby_file do
type "show_source Foo"
type "exit!"
end

# The input should work as a command
assert_include output, "From: #{@ruby_file.path}:1"
# Therefore, it should not raise a warning
assert_not_include output,
'If you intended to run it as a command, please check if the syntax is correct.'

output = run_ruby_file do
type "show_source = 'foo'"
type "show_source + 'bar'"
type "exit!"
end

# The input should work as Ruby code
assert_include(output, '=> "foobar"')
# Therefore, it should not raise a warning either
assert_not_include output,
'If you intended to run it as a command, please check if the syntax is correct.'
end
end

class IrbIOConfigurationTest < TestCase
Expand Down

0 comments on commit c22fe6b

Please sign in to comment.