Skip to content

Commit fb15250

Browse files
authored
Don't create tokens by hand (#1696)
There is some awkward code that dances around the fact that the tokens for a method actually contain a 3 extra tokens that don't exist in the source code. Now `RipperStateLex` is only referenced to actually parse, rest is kept internal
1 parent 9bab62f commit fb15250

5 files changed

Lines changed: 163 additions & 84 deletions

File tree

lib/rdoc/generator/markup.rb

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -86,57 +86,60 @@ class RDoc::CodeObject
8686
class RDoc::MethodAttr
8787

8888
##
89-
# Prepend +src+ with line numbers. Relies on the first line of a source
90-
# code listing having:
91-
#
92-
# # File xxxxx, line dddd
93-
#
94-
# If it has this comment then line numbers are added to +src+ and the <tt>,
95-
# line dddd</tt> portion of the comment is removed.
89+
# Prepend +src+ with line numbers.
9690

9791
def add_line_numbers(src)
98-
return unless src.sub!(/\A(.*)(, line (\d+))/, '\1')
99-
first = $3.to_i - 1
100-
last = first + src.count("\n")
101-
size = last.to_s.length
92+
return if src.empty? || !line
93+
start_line = line
94+
end_line = start_line + src.count("\n")
95+
number_digits = end_line.to_s.length
10296

103-
line = first
97+
current_line = start_line
10498
src.gsub!(/^/) do
105-
res = if line == first then
106-
" " * (size + 1)
107-
else
108-
"<span class=\"line-num\">%2$*1$d</span> " % [size, line]
109-
end
99+
res = "<span class=\"line-num\">#{current_line.to_s.rjust(number_digits)}</span> "
110100

111-
line += 1
101+
current_line += 1
112102
res
113103
end
114104
end
115105

106+
##
107+
# Prepend +src+ with a comment that declares its location in the source.
108+
109+
def add_location_comment(src)
110+
path = CGI.escapeHTML(file.relative_name)
111+
if options.line_numbers && !src.empty?
112+
src.prepend("<span class=\"ruby-comment\"># File #{path}</span>\n")
113+
else
114+
src.prepend("<span class=\"ruby-comment\"># File #{path}, line #{line}</span>\n")
115+
end
116+
end
117+
116118
##
117119
# Turns the method's token stream into HTML.
118120
#
119121
# Prepends line numbers if +options.line_numbers+ is true.
120122

121123
def markup_code
122-
return '' unless @token_stream
124+
return '' if !@token_stream
123125

124126
src = RDoc::TokenStream.to_html @token_stream
125127

128+
# add initial whitespace so that the indent gets calculated correctly
129+
src.prepend(' ' * @token_stream.first[:char_no]) if source_language == 'ruby' && @token_stream.first
130+
126131
# dedent the source
127-
indent = src.length
128-
lines = src.lines.to_a
129-
lines.shift if src =~ /\A.*#\ *File/i # remove '# File' comment
130-
lines.each do |line|
131-
if line =~ /^ *(?=\S)/
132-
n = $~.end(0)
133-
indent = n if n < indent
134-
break if n == 0
135-
end
132+
common_indent = src.length
133+
src.scan(/^ *(?=\S)/) do |whitespace|
134+
common_indent = whitespace.length if whitespace.length < common_indent
135+
break if common_indent == 0
136136
end
137-
src.gsub!(/^#{' ' * indent}/, '') if indent > 0
137+
src.gsub!(/^#{' ' * common_indent}/, '') if common_indent > 0
138138

139-
add_line_numbers(src) if options.line_numbers
139+
if source_language == 'ruby'
140+
add_line_numbers(src) if options.line_numbers
141+
add_location_comment(src)
142+
end
140143

141144
src
142145
end

lib/rdoc/parser/ruby.rb

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ def parse_comment_tomdoc(container, comment, line_no, start_line)
314314

315315
meth.start_collecting_tokens(:ruby)
316316
node = @line_nodes[line_no]
317-
tokens = node ? visible_tokens_from_location(node.location) : [file_line_comment_token(start_line)]
317+
tokens = node ? visible_tokens_from_location(node.location) : []
318318
tokens.each { |token| meth.token_stream << token }
319319

320320
container.add_method meth
@@ -385,7 +385,7 @@ def handle_meta_method_comment(comment, directives, node)
385385
tokens = visible_tokens_from_location(node.location)
386386
line_no = node.location.start_line
387387
else
388-
tokens = [file_line_comment_token(line_no)]
388+
tokens = []
389389
end
390390
internal_add_method(
391391
method_name,
@@ -498,23 +498,13 @@ def slice_tokens(start_pos, end_pos) # :nodoc:
498498
tokens
499499
end
500500

501-
def file_line_comment_token(line_no) # :nodoc:
502-
position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no - 1, 0, :on_comment)
503-
position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}"
504-
position_comment
505-
end
506-
507501
# Returns tokens from the given location
508502

509503
def visible_tokens_from_location(location)
510-
position_comment = file_line_comment_token(location.start_line)
511-
newline_token = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n")
512-
indent_token = RDoc::Parser::RipperStateLex::Token.new(location.start_line, 0, :on_sp, ' ' * location.start_character_column)
513-
tokens = slice_tokens(
504+
slice_tokens(
514505
[location.start_line, location.start_character_column],
515506
[location.end_line, location.end_character_column]
516507
)
517-
[position_comment, newline_token, indent_token, *tokens]
518508
end
519509

520510
# Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar`

test/rdoc/code_object/any_method_test.rb

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -148,51 +148,11 @@ def test_call_seq_returns_nil_if_alias_is_missing_from_call_seq
148148
assert_nil(alias_to_method.call_seq)
149149
end
150150

151-
def test_markup_code
152-
tokens = [
153-
{ :line_no => 0, :char_no => 0, :kind => :on_const, :text => 'CONSTANT' },
154-
]
155-
156-
@c2_a.collect_tokens(:ruby)
157-
@c2_a.add_tokens(tokens)
158-
159-
expected = '<span class="ruby-constant">CONSTANT</span>'
160-
161-
assert_equal expected, @c2_a.markup_code
162-
end
163-
164-
def test_markup_code_with_line_numbers
165-
position_comment = "# File #{@file_name}, line 1"
166-
tokens = [
167-
{ :line_no => 1, :char_no => 0, :kind => :on_comment, :text => position_comment },
168-
{ :line_no => 1, :char_no => position_comment.size, :kind => :on_nl, :text => "\n" },
169-
{ :line_no => 2, :char_no => 0, :kind => :on_const, :text => 'A' },
170-
{ :line_no => 2, :char_no => 1, :kind => :on_nl, :text => "\n" },
171-
{ :line_no => 3, :char_no => 0, :kind => :on_const, :text => 'B' }
172-
]
173-
174-
@c2_a.collect_tokens(:ruby)
175-
@c2_a.add_tokens(tokens)
176-
177-
assert_equal <<-EXPECTED.chomp, @c2_a.markup_code
178-
<span class="ruby-comment"># File xref_data.rb, line 1</span>
179-
<span class="ruby-constant">A</span>
180-
<span class="ruby-constant">B</span>
181-
EXPECTED
182-
183-
@c2_a.options.line_numbers = true
184-
assert_equal <<-EXPECTED.chomp, @c2_a.markup_code
185-
<span class="ruby-comment"># File xref_data.rb</span>
186-
<span class="line-num">1</span> <span class="ruby-constant">A</span>
187-
<span class="line-num">2</span> <span class="ruby-constant">B</span>
188-
EXPECTED
189-
end
190-
191151
def test_markup_code_empty
192152
assert_equal '', @c2_a.markup_code
193153
end
194154

195-
def test_markup_code_with_variable_expansion
155+
def test_param_seq_with_variable_expansion
196156
m = RDoc::AnyMethod.new nil, 'method'
197157
m.parent = @c1
198158
m.block_params = '"Hello, #{world}", yield_arg'

test/rdoc/parser/c_test.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,6 +2215,33 @@ def test_markup_format_override
22152215
assert_equal("markdown", klass.attributes.find {|a| a.name == "default_format"}.comment.format)
22162216
end
22172217

2218+
def test_markup_code
2219+
# Should not generate line numbers
2220+
@top_level.store.options.line_numbers = true
2221+
parser = util_parser <<~C
2222+
static VALUE
2223+
rb_hash_has_value(VALUE hash, VALUE val) {
2224+
return Qtrue;
2225+
}
2226+
2227+
Init_Hash(void)
2228+
{
2229+
rb_define_method(rb_cHash, "value?", rb_hash_has_value, 1);
2230+
}
2231+
C
2232+
parser.scan
2233+
2234+
hash = @store.classes_hash['Hash']
2235+
value_method = hash.method_list.find { |m| m.name == 'value?' }
2236+
2237+
assert_equal(<<~EXPECTED.chomp, value_method.markup_code)
2238+
static VALUE
2239+
rb_hash_has_value(VALUE hash, VALUE val) {
2240+
return Qtrue;
2241+
}
2242+
EXPECTED
2243+
end
2244+
22182245
def test_clear_file_contributions_removes_c_methods
22192246
content = <<~C
22202247
/* Document-class: Foo */

test/rdoc/parser/ruby_test.rb

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2529,6 +2529,105 @@ def m2; end
25292529
assert_equal "ARGF.readlines(a)\nARGF.readlines(b)\nARGF.readlines(c)\nARGF.readlines(d)", m2.call_seq.chomp
25302530
end
25312531

2532+
def test_markup_code
2533+
util_parser <<~RUBY
2534+
class Foo
2535+
def bar
2536+
end
2537+
end
2538+
RUBY
2539+
2540+
m1, = @top_level.classes.first.method_list
2541+
2542+
assert_equal <<~EXPECTED.chomp, m1.markup_code
2543+
<span class="ruby-comment"># File #{@filename}, line 2</span>
2544+
<span class="ruby-keyword">def</span> <span class="ruby-identifier">bar</span>
2545+
<span class="ruby-keyword">end</span>
2546+
EXPECTED
2547+
end
2548+
2549+
def test_markup_code_with_line_numbers
2550+
@top_level.store.options.line_numbers = true
2551+
util_parser <<~RUBY
2552+
class Foo
2553+
def bar
2554+
end
2555+
end
2556+
RUBY
2557+
2558+
m1, = @top_level.classes.first.method_list
2559+
2560+
assert_equal <<~EXPECTED.chomp, m1.markup_code
2561+
<span class="ruby-comment"># File #{@filename}</span>
2562+
<span class="line-num">2</span> <span class="ruby-keyword">def</span> <span class="ruby-identifier">bar</span>
2563+
<span class="line-num">3</span> <span class="ruby-keyword">end</span>
2564+
EXPECTED
2565+
end
2566+
2567+
def test_markup_code_dedent
2568+
util_parser <<~RUBY
2569+
class Foo
2570+
def bar
2571+
end
2572+
2573+
private
2574+
def baz
2575+
end
2576+
end
2577+
RUBY
2578+
m1, m2 = @top_level.classes.first.method_list
2579+
2580+
assert_equal(<<~EXPECTED.chomp, m1.markup_code)
2581+
<span class="ruby-comment"># File #{@filename}, line 2</span>
2582+
<span class="ruby-keyword">def</span> <span class="ruby-identifier">bar</span>
2583+
<span class="ruby-keyword">end</span>
2584+
EXPECTED
2585+
assert_equal(<<~EXPECTED.chomp, m2.markup_code)
2586+
<span class="ruby-comment"># File #{@filename}, line 6</span>
2587+
<span class="ruby-keyword">def</span> <span class="ruby-identifier">baz</span>
2588+
<span class="ruby-keyword">end</span>
2589+
EXPECTED
2590+
end
2591+
2592+
def test_markup_code_empty
2593+
util_parser <<~RUBY
2594+
class Foo
2595+
##
2596+
# :method: ghost_method
2597+
2598+
##
2599+
# :method:
2600+
# :call-seq: ghost_method2() -> Integer
2601+
end
2602+
RUBY
2603+
2604+
m1, m2 = @top_level.classes.first.method_list
2605+
assert_equal(
2606+
"<span class=\"ruby-comment\"># File #{@filename}, line 3</span>",
2607+
m1.markup_code.chomp
2608+
)
2609+
assert_equal(
2610+
"<span class=\"ruby-comment\"># File #{@filename}, line 6</span>",
2611+
m2.markup_code.chomp
2612+
)
2613+
end
2614+
2615+
def test_markup_code_empty_line_number
2616+
@top_level.store.options.line_numbers = true
2617+
util_parser <<~RUBY
2618+
class Foo
2619+
##
2620+
# :method: ghost_method
2621+
end
2622+
RUBY
2623+
2624+
m, = @top_level.classes.first.method_list
2625+
assert_equal(
2626+
"<span class=\"ruby-comment\"># File #{@filename}, line 3</span>",
2627+
m.markup_code.chomp
2628+
)
2629+
end
2630+
25322631
def util_parser(content)
25332632
@parser = RDoc::Parser::Ruby.new @top_level, content, @options, @stats
25342633
@parser.scan

0 commit comments

Comments
 (0)