diff --git a/lib/irb.rb b/lib/irb.rb index 6ac0c49f5..9e11c874d 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1036,6 +1036,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 @@ -1119,22 +1122,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 diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index b12110600..4a7f4a7dc 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -2,7 +2,7 @@ module IRB class Statement - attr_reader :code + attr_reader :code, :warning def is_assignment? raise NotImplementedError @@ -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? @@ -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) diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 499889067..d0e73036b 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -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