[ruby/irb] Gracefully handle incorrect command aliases
(https://github.com/ruby/irb/pull/1059) * Gracefully handle incorrect command aliases Even if the aliased target is a helper method or does not exist, IRB should not crash. This commit warns users in such cases and treat the input as normal expression. * Streamline command parsing and introduce warnings for incorrect command aliases https://github.com/ruby/irb/commit/9fc14eb74b
This commit is contained in:
parent
4a2702dafb
commit
039446f601
16
lib/irb.rb
16
lib/irb.rb
@ -269,29 +269,25 @@ module IRB
|
|||||||
loop do
|
loop do
|
||||||
code = readmultiline
|
code = readmultiline
|
||||||
break unless code
|
break unless code
|
||||||
yield build_statement(code), @line_no
|
yield parse_input(code), @line_no
|
||||||
@line_no += code.count("\n")
|
@line_no += code.count("\n")
|
||||||
rescue RubyLex::TerminateLineInput
|
rescue RubyLex::TerminateLineInput
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_statement(code)
|
def parse_input(code)
|
||||||
if code.match?(/\A\n*\z/)
|
if code.match?(/\A\n*\z/)
|
||||||
return Statement::EmptyInput.new
|
return Statement::EmptyInput.new
|
||||||
end
|
end
|
||||||
|
|
||||||
code = code.dup.force_encoding(@context.io.encoding)
|
code = code.dup.force_encoding(@context.io.encoding)
|
||||||
if (command, arg = @context.parse_command(code))
|
is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables)
|
||||||
command_class = Command.load_command(command)
|
|
||||||
Statement::Command.new(code, command_class, arg)
|
@context.parse_input(code, is_assignment_expression)
|
||||||
else
|
|
||||||
is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables)
|
|
||||||
Statement::Expression.new(code, is_assignment_expression)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def command?(code)
|
def command?(code)
|
||||||
!!@context.parse_command(code)
|
parse_input(code).is_a?(Statement::Command)
|
||||||
end
|
end
|
||||||
|
|
||||||
def configure_io
|
def configure_io
|
||||||
|
@ -600,6 +600,8 @@ module IRB
|
|||||||
set_last_value(result)
|
set_last_value(result)
|
||||||
when Statement::Command
|
when Statement::Command
|
||||||
statement.command_class.execute(self, statement.arg)
|
statement.command_class.execute(self, statement.arg)
|
||||||
|
when Statement::IncorrectAlias
|
||||||
|
warn statement.message
|
||||||
end
|
end
|
||||||
|
|
||||||
nil
|
nil
|
||||||
@ -633,35 +635,60 @@ module IRB
|
|||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_command(code)
|
def parse_input(code, is_assignment_expression)
|
||||||
command_name, arg = code.strip.split(/\s+/, 2)
|
command_name, arg = code.strip.split(/\s+/, 2)
|
||||||
return unless code.lines.size == 1 && command_name
|
|
||||||
|
|
||||||
arg ||= ''
|
arg ||= ''
|
||||||
command = command_name.to_sym
|
|
||||||
# Command aliases are always command. example: $, @
|
# command can only be 1 line
|
||||||
if (alias_name = command_aliases[command])
|
if code.lines.size != 1 ||
|
||||||
return [alias_name, arg]
|
# command name is required
|
||||||
|
command_name.nil? ||
|
||||||
|
# local variable have precedence over command
|
||||||
|
local_variables.include?(command_name.to_sym) ||
|
||||||
|
# assignment expression is not a command
|
||||||
|
(is_assignment_expression ||
|
||||||
|
(arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/)))
|
||||||
|
return Statement::Expression.new(code, is_assignment_expression)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Assignment-like expression is not a command
|
command = command_name.to_sym
|
||||||
return if arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/)
|
|
||||||
|
|
||||||
# Local variable have precedence over command
|
# Check command aliases
|
||||||
return if local_variables.include?(command)
|
if aliased_name = command_aliases[command]
|
||||||
|
if command_class = Command.load_command(aliased_name)
|
||||||
|
command = aliased_name
|
||||||
|
elsif HelperMethod.helper_methods[aliased_name]
|
||||||
|
message = <<~MESSAGE
|
||||||
|
Using command alias `#{command}` for helper method `#{aliased_name}` is not supported.
|
||||||
|
Please check the value of `IRB.conf[:COMMAND_ALIASES]`.
|
||||||
|
MESSAGE
|
||||||
|
return Statement::IncorrectAlias.new(message)
|
||||||
|
else
|
||||||
|
message = <<~MESSAGE
|
||||||
|
You're trying to use command alias `#{command}` for command `#{aliased_name}`, but `#{aliased_name}` does not exist.
|
||||||
|
Please check the value of `IRB.conf[:COMMAND_ALIASES]`.
|
||||||
|
MESSAGE
|
||||||
|
return Statement::IncorrectAlias.new(message)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
command_class = Command.load_command(command)
|
||||||
|
end
|
||||||
|
|
||||||
# Check visibility
|
# Check visibility
|
||||||
public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false
|
public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false
|
||||||
private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false
|
private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false
|
||||||
if Command.execute_as_command?(command, public_method: public_method, private_method: private_method)
|
if command_class && Command.execute_as_command?(command, public_method: public_method, private_method: private_method)
|
||||||
[command, arg]
|
Statement::Command.new(code, command_class, arg)
|
||||||
|
else
|
||||||
|
Statement::Expression.new(code, is_assignment_expression)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def colorize_input(input, complete:)
|
def colorize_input(input, complete:)
|
||||||
if IRB.conf[:USE_COLORIZE] && IRB::Color.colorable?
|
if IRB.conf[:USE_COLORIZE] && IRB::Color.colorable?
|
||||||
lvars = local_variables || []
|
lvars = local_variables || []
|
||||||
if parse_command(input)
|
parsed_input = parse_input(input, false)
|
||||||
|
if parsed_input.is_a?(Statement::Command)
|
||||||
name, sep, arg = input.split(/(\s+)/, 2)
|
name, sep, arg = input.split(/(\s+)/, 2)
|
||||||
arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars)
|
arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars)
|
||||||
"#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}"
|
"#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}"
|
||||||
|
@ -54,6 +54,27 @@ module IRB
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class IncorrectAlias < Statement
|
||||||
|
attr_reader :message
|
||||||
|
|
||||||
|
def initialize(message)
|
||||||
|
@code = ""
|
||||||
|
@message = message
|
||||||
|
end
|
||||||
|
|
||||||
|
def should_be_handled_by_debugger?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_assignment?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def suppresses_echo?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Command < Statement
|
class Command < Statement
|
||||||
attr_reader :command_class, :arg
|
attr_reader :command_class, :arg
|
||||||
|
|
||||||
|
50
test/irb/command/test_command_aliasing.rb
Normal file
50
test/irb/command/test_command_aliasing.rb
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "tempfile"
|
||||||
|
require_relative "../helper"
|
||||||
|
|
||||||
|
module TestIRB
|
||||||
|
class CommandAliasingTest < IntegrationTestCase
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
write_rc <<~RUBY
|
||||||
|
IRB.conf[:COMMAND_ALIASES] = {
|
||||||
|
:c => :conf, # alias to helper method
|
||||||
|
:f => :foo
|
||||||
|
}
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
write_ruby <<~'RUBY'
|
||||||
|
binding.irb
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_aliasing_to_helper_method_triggers_warning
|
||||||
|
out = run_ruby_file do
|
||||||
|
type "c"
|
||||||
|
type "exit"
|
||||||
|
end
|
||||||
|
assert_include(out, "Using command alias `c` for helper method `conf` is not supported.")
|
||||||
|
assert_not_include(out, "Maybe IRB bug!")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_alias_to_non_existent_command_triggers_warning
|
||||||
|
message = "You're trying to use command alias `f` for command `foo`, but `foo` does not exist."
|
||||||
|
out = run_ruby_file do
|
||||||
|
type "f"
|
||||||
|
type "exit"
|
||||||
|
end
|
||||||
|
assert_include(out, message)
|
||||||
|
assert_not_include(out, "Maybe IRB bug!")
|
||||||
|
|
||||||
|
# Local variables take precedence over command aliases
|
||||||
|
out = run_ruby_file do
|
||||||
|
type "f = 123"
|
||||||
|
type "f"
|
||||||
|
type "exit"
|
||||||
|
end
|
||||||
|
assert_not_include(out, message)
|
||||||
|
assert_not_include(out, "Maybe IRB bug!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user