Skip to content

Commit 039446f

Browse files
st0012matzbot
authored andcommitted
[ruby/irb] Gracefully handle incorrect command aliases
(ruby/irb#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 ruby/irb@9fc14eb74b
1 parent 4a2702d commit 039446f

File tree

4 files changed

+118
-24
lines changed

4 files changed

+118
-24
lines changed

lib/irb.rb

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -269,29 +269,25 @@ def each_top_level_statement
269269
loop do
270270
code = readmultiline
271271
break unless code
272-
yield build_statement(code), @line_no
272+
yield parse_input(code), @line_no
273273
@line_no += code.count("\n")
274274
rescue RubyLex::TerminateLineInput
275275
end
276276
end
277277

278-
def build_statement(code)
278+
def parse_input(code)
279279
if code.match?(/\A\n*\z/)
280280
return Statement::EmptyInput.new
281281
end
282282

283283
code = code.dup.force_encoding(@context.io.encoding)
284-
if (command, arg = @context.parse_command(code))
285-
command_class = Command.load_command(command)
286-
Statement::Command.new(code, command_class, arg)
287-
else
288-
is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables)
289-
Statement::Expression.new(code, is_assignment_expression)
290-
end
284+
is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables)
285+
286+
@context.parse_input(code, is_assignment_expression)
291287
end
292288

293289
def command?(code)
294-
!!@context.parse_command(code)
290+
parse_input(code).is_a?(Statement::Command)
295291
end
296292

297293
def configure_io

lib/irb/context.rb

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ def evaluate(statement, line_no) # :nodoc:
600600
set_last_value(result)
601601
when Statement::Command
602602
statement.command_class.execute(self, statement.arg)
603+
when Statement::IncorrectAlias
604+
warn statement.message
603605
end
604606

605607
nil
@@ -633,35 +635,60 @@ def evaluate_expression(code, line_no) # :nodoc:
633635
result
634636
end
635637

636-
def parse_command(code)
638+
def parse_input(code, is_assignment_expression)
637639
command_name, arg = code.strip.split(/\s+/, 2)
638-
return unless code.lines.size == 1 && command_name
639-
640640
arg ||= ''
641-
command = command_name.to_sym
642-
# Command aliases are always command. example: $, @
643-
if (alias_name = command_aliases[command])
644-
return [alias_name, arg]
641+
642+
# command can only be 1 line
643+
if code.lines.size != 1 ||
644+
# command name is required
645+
command_name.nil? ||
646+
# local variable have precedence over command
647+
local_variables.include?(command_name.to_sym) ||
648+
# assignment expression is not a command
649+
(is_assignment_expression ||
650+
(arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/)))
651+
return Statement::Expression.new(code, is_assignment_expression)
645652
end
646653

647-
# Assignment-like expression is not a command
648-
return if arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/)
654+
command = command_name.to_sym
649655

650-
# Local variable have precedence over command
651-
return if local_variables.include?(command)
656+
# Check command aliases
657+
if aliased_name = command_aliases[command]
658+
if command_class = Command.load_command(aliased_name)
659+
command = aliased_name
660+
elsif HelperMethod.helper_methods[aliased_name]
661+
message = <<~MESSAGE
662+
Using command alias `#{command}` for helper method `#{aliased_name}` is not supported.
663+
Please check the value of `IRB.conf[:COMMAND_ALIASES]`.
664+
MESSAGE
665+
return Statement::IncorrectAlias.new(message)
666+
else
667+
message = <<~MESSAGE
668+
You're trying to use command alias `#{command}` for command `#{aliased_name}`, but `#{aliased_name}` does not exist.
669+
Please check the value of `IRB.conf[:COMMAND_ALIASES]`.
670+
MESSAGE
671+
return Statement::IncorrectAlias.new(message)
672+
end
673+
else
674+
command_class = Command.load_command(command)
675+
end
652676

653677
# Check visibility
654678
public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false
655679
private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false
656-
if Command.execute_as_command?(command, public_method: public_method, private_method: private_method)
657-
[command, arg]
680+
if command_class && Command.execute_as_command?(command, public_method: public_method, private_method: private_method)
681+
Statement::Command.new(code, command_class, arg)
682+
else
683+
Statement::Expression.new(code, is_assignment_expression)
658684
end
659685
end
660686

661687
def colorize_input(input, complete:)
662688
if IRB.conf[:USE_COLORIZE] && IRB::Color.colorable?
663689
lvars = local_variables || []
664-
if parse_command(input)
690+
parsed_input = parse_input(input, false)
691+
if parsed_input.is_a?(Statement::Command)
665692
name, sep, arg = input.split(/(\s+)/, 2)
666693
arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars)
667694
"#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}"

lib/irb/statement.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ def is_assignment?
5454
end
5555
end
5656

57+
class IncorrectAlias < Statement
58+
attr_reader :message
59+
60+
def initialize(message)
61+
@code = ""
62+
@message = message
63+
end
64+
65+
def should_be_handled_by_debugger?
66+
false
67+
end
68+
69+
def is_assignment?
70+
false
71+
end
72+
73+
def suppresses_echo?
74+
true
75+
end
76+
end
77+
5778
class Command < Statement
5879
attr_reader :command_class, :arg
5980

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# frozen_string_literal: true
2+
3+
require "tempfile"
4+
require_relative "../helper"
5+
6+
module TestIRB
7+
class CommandAliasingTest < IntegrationTestCase
8+
def setup
9+
super
10+
write_rc <<~RUBY
11+
IRB.conf[:COMMAND_ALIASES] = {
12+
:c => :conf, # alias to helper method
13+
:f => :foo
14+
}
15+
RUBY
16+
17+
write_ruby <<~'RUBY'
18+
binding.irb
19+
RUBY
20+
end
21+
22+
def test_aliasing_to_helper_method_triggers_warning
23+
out = run_ruby_file do
24+
type "c"
25+
type "exit"
26+
end
27+
assert_include(out, "Using command alias `c` for helper method `conf` is not supported.")
28+
assert_not_include(out, "Maybe IRB bug!")
29+
end
30+
31+
def test_alias_to_non_existent_command_triggers_warning
32+
message = "You're trying to use command alias `f` for command `foo`, but `foo` does not exist."
33+
out = run_ruby_file do
34+
type "f"
35+
type "exit"
36+
end
37+
assert_include(out, message)
38+
assert_not_include(out, "Maybe IRB bug!")
39+
40+
# Local variables take precedence over command aliases
41+
out = run_ruby_file do
42+
type "f = 123"
43+
type "f"
44+
type "exit"
45+
end
46+
assert_not_include(out, message)
47+
assert_not_include(out, "Maybe IRB bug!")
48+
end
49+
end
50+
end

0 commit comments

Comments
 (0)