Skip to content

Commit 04198d3

Browse files
committed
Behavior of "include" or "extend" as a method
In RDoc, "include" or "extend" can receive only constant. But those sometimes receive dynamic value through complex statements. This commit fixes it.
1 parent 4c7e445 commit 04198d3

File tree

2 files changed

+218
-15
lines changed

2 files changed

+218
-15
lines changed

lib/rdoc/parser/ruby.rb

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -446,28 +446,80 @@ def get_constant
446446
end
447447

448448
##
449-
# Get a constant that may be surrounded by parens
449+
# Get an included module that may be surrounded by parens
450450

451-
def get_constant_with_optional_parens
451+
def get_included_module_with_optional_parens
452452
skip_tkspace false
453+
get_tkread
454+
tk = get_tk
455+
end_token = get_end_token tk
456+
return '' unless end_token
453457

454458
nest = 0
459+
continue = false
460+
only_constant = true
455461

456-
while :on_lparen == (tk = peek_tk)[:kind] do
457-
get_tk
458-
skip_tkspace
459-
nest += 1
460-
end
461-
462-
name = get_constant
463-
464-
while nest > 0
465-
skip_tkspace
462+
while tk != nil do
463+
# bracket?
464+
is_element_of_constant = false
465+
case tk[:kind]
466+
when :on_semicolon then
467+
break if nest == 0
468+
when :on_lbrace then
469+
nest += 1
470+
when :on_rbrace then
471+
nest -= 1
472+
if nest <= 0
473+
# we might have a.each { |i| yield i }
474+
unget_tk(tk) if nest < 0
475+
break
476+
end
477+
when :on_lparen then
478+
nest += 1
479+
when end_token[:kind] then
480+
if end_token[:kind] == :on_rparen
481+
nest -= 1
482+
break if nest <= 0
483+
else
484+
break if nest <= 0
485+
end
486+
when :on_rparen then
487+
nest -= 1
488+
when :on_comment, :on_embdoc then
489+
@read.pop
490+
if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and
491+
(!continue or (tk[:state] & RDoc::Parser::RipperStateLex::EXPR_LABEL) != 0) then
492+
break if !continue and nest <= 0
493+
end
494+
when :on_comma then
495+
continue = true
496+
when :on_ident then
497+
continue = false if continue
498+
when :on_kw then
499+
case tk[:text]
500+
when 'def', 'do', 'case', 'for', 'begin', 'class', 'module'
501+
nest += 1
502+
when 'if', 'unless', 'while', 'until', 'rescue'
503+
# postfix if/unless/while/until/rescue must be EXPR_LABEL
504+
nest += 1 unless (tk[:state] & RDoc::Parser::RipperStateLex::EXPR_LABEL) != 0
505+
when 'end'
506+
nest -= 1
507+
break if nest == 0
508+
end
509+
when :on_const then
510+
is_element_of_constant = true
511+
when :on_op then
512+
is_element_of_constant = true if '::' == tk[:text]
513+
end
514+
only_constant = false unless is_element_of_constant
466515
tk = get_tk
467-
nest -= 1 if :on_rparen == tk[:kind]
468516
end
469517

470-
name
518+
if only_constant
519+
get_tkread_clean(/\s+/, ' ')
520+
else
521+
''
522+
end
471523
end
472524

473525
##
@@ -1119,7 +1171,7 @@ def parse_extend_or_include klass, container, comment # :nodoc:
11191171
loop do
11201172
skip_tkspace_comment
11211173

1122-
name = get_constant_with_optional_parens
1174+
name = get_included_module_with_optional_parens
11231175

11241176
unless name.empty? then
11251177
obj = container.add klass, name, comment

test/test_rdoc_parser_ruby.rb

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4044,4 +4044,155 @@ class Defined
40444044
assert_equal ['A', 'B', 'B::C'], visible
40454045
end
40464046

4047+
def test_parse_include_by_dynamic_definition
4048+
util_parser <<-CLASS
4049+
module A
4050+
class B
4051+
include(Module.new do
4052+
def e(m)
4053+
end
4054+
end)
4055+
end
4056+
4057+
class C
4058+
end
4059+
4060+
class D
4061+
end
4062+
end
4063+
CLASS
4064+
4065+
@parser.scan
4066+
4067+
a = @store.find_module_named 'A'
4068+
assert_equal 'A', a.full_name
4069+
a_b = a.find_class_named 'B'
4070+
assert_equal 'A::B', a_b.full_name
4071+
a_c = a.find_class_named 'C'
4072+
assert_equal 'A::C', a_c.full_name
4073+
a_d = a.find_class_named 'D'
4074+
assert_equal 'A::D', a_d.full_name
4075+
end
4076+
4077+
def test_parse_include_by_dynamic_definition_without_paren
4078+
util_parser <<-CLASS
4079+
module A
4080+
class B
4081+
include(Module.new do
4082+
def e m
4083+
end
4084+
end)
4085+
end
4086+
4087+
class C
4088+
end
4089+
4090+
class D
4091+
end
4092+
end
4093+
CLASS
4094+
4095+
@parser.scan
4096+
4097+
a = @store.find_module_named 'A'
4098+
assert_equal 'A', a.full_name
4099+
a_b = a.find_class_named 'B'
4100+
assert_equal 'A::B', a_b.full_name
4101+
a_c = a.find_class_named 'C'
4102+
assert_equal 'A::C', a_c.full_name
4103+
a_d = a.find_class_named 'D'
4104+
assert_equal 'A::D', a_d.full_name
4105+
end
4106+
4107+
def test_parse_include_by_dynamic_definition_via_variable
4108+
util_parser <<-CLASS
4109+
module A
4110+
class B
4111+
m = Module.new do
4112+
def e(m)
4113+
end
4114+
end
4115+
include m
4116+
end
4117+
4118+
class C
4119+
end
4120+
4121+
class D
4122+
end
4123+
end
4124+
CLASS
4125+
4126+
@parser.scan
4127+
4128+
a = @store.find_module_named 'A'
4129+
assert_equal 'A', a.full_name
4130+
a_b = a.find_class_named 'B'
4131+
assert_equal 'A::B', a_b.full_name
4132+
a_c = a.find_class_named 'C'
4133+
assert_equal 'A::C', a_c.full_name
4134+
a_d = a.find_class_named 'D'
4135+
assert_equal 'A::D', a_d.full_name
4136+
end
4137+
4138+
def test_parse_include_by_dynamic_definition_with_brace
4139+
util_parser <<-CLASS
4140+
module A
4141+
class B
4142+
extend(e {
4143+
def f(g)
4144+
end
4145+
})
4146+
end
4147+
4148+
class C
4149+
end
4150+
4151+
class D
4152+
end
4153+
end
4154+
CLASS
4155+
4156+
@parser.scan
4157+
4158+
a = @store.find_module_named 'A'
4159+
assert_equal 'A', a.full_name
4160+
a_b = a.find_class_named 'B'
4161+
assert_equal 'A::B', a_b.full_name
4162+
a_c = a.find_class_named 'C'
4163+
assert_equal 'A::C', a_c.full_name
4164+
a_d = a.find_class_named 'D'
4165+
assert_equal 'A::D', a_d.full_name
4166+
end
4167+
4168+
def test_parse_include_by_dynamic_definition_directly
4169+
util_parser <<-CLASS
4170+
module A
4171+
class B
4172+
include Module.new do
4173+
def e m
4174+
end
4175+
end
4176+
end
4177+
4178+
class C
4179+
end
4180+
4181+
class D
4182+
end
4183+
end
4184+
CLASS
4185+
4186+
@parser.scan
4187+
4188+
a = @store.find_module_named 'A'
4189+
assert_equal 'A', a.full_name
4190+
a_b = a.find_class_named 'B'
4191+
assert_equal 'A::B', a_b.full_name
4192+
a_c = a.find_class_named 'C'
4193+
assert_equal 'A::C', a_c.full_name
4194+
a_d = a.find_class_named 'D'
4195+
assert_equal 'A::D', a_d.full_name
4196+
end
4197+
40474198
end

0 commit comments

Comments
 (0)