Skip to content

Commit 5c4b1b9

Browse files
committed
Replace Ruby pseudo-code coverage report with file-centric format
The `rdoc -C` coverage report previously displayed undocumented items using Ruby syntax (`class ... end`, `def ...; end`). For C source files like Ruby's object.c, this made the output appear as if the C file was being treated as a Ruby script. Replace with a file-centric listing that groups undocumented items by source file and type: object.c: Class: Refinement Method: Module#name object.c:135 Undocumented params: mod.name->stringornil - Group items by source file, sorted alphabetically - Within each file, group by type (Class, Module, Constant, Attribute, Method) - Sort items by line number within each type group - Use ClassName#method / ClassName.method notation - Show clickable file:line references, column-aligned - Return plain strings from report/summary instead of Markup objects
1 parent b5bcd84 commit 5c4b1b9

File tree

3 files changed

+398
-391
lines changed

3 files changed

+398
-391
lines changed

lib/rdoc/rdoc.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ def document(options)
497497
if @options.coverage_report then
498498
puts
499499

500-
puts @stats.report.accept RDoc::Markup::ToRdoc.new
500+
puts @stats.report
501501
elsif file_info.empty? then
502502
$stderr.puts "\nNo newer files." unless @options.quiet
503503
else
@@ -510,7 +510,7 @@ def document(options)
510510

511511
if @stats and (@options.coverage_report or not @options.quiet) then
512512
puts
513-
puts @stats.summary.accept RDoc::Markup::ToRdoc.new
513+
puts @stats.summary
514514
end
515515

516516
exit @stats.fully_documented? if @options.coverage_report

lib/rdoc/stats.rb

Lines changed: 137 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ class RDoc::Stats
77

88
include RDoc::Text
99

10+
##
11+
# Display order for item types in the coverage report
12+
13+
TYPE_ORDER = %w[Class Module Constant Attribute Method].freeze
14+
15+
##
16+
# Message displayed when all items are documented
17+
18+
GREAT_JOB_MESSAGE = <<~MSG
19+
100% documentation!
20+
Great Job!
21+
MSG
22+
1023
##
1124
# Output level for the coverage report
1225

@@ -193,18 +206,6 @@ def fully_documented?
193206
@fully_documented
194207
end
195208

196-
##
197-
# A report that says you did a great job!
198-
199-
def great_job
200-
report = RDoc::Markup::Document.new
201-
202-
report << RDoc::Markup::Paragraph.new('100% documentation!')
203-
report << RDoc::Markup::Paragraph.new('Great Job!')
204-
205-
report
206-
end
207-
208209
##
209210
# Calculates the percentage of items documented.
210211

@@ -230,164 +231,184 @@ def report
230231
if @coverage_level.zero? then
231232
calculate
232233

233-
return great_job if @num_items == @doc_items
234+
return GREAT_JOB_MESSAGE if @num_items == @doc_items
234235
end
235236

236-
ucm = @store.unique_classes_and_modules
237+
items, empty_classes = collect_undocumented_items
237238

238-
report = RDoc::Markup::Document.new
239-
report << RDoc::Markup::Paragraph.new('The following items are not documented:')
240-
report << RDoc::Markup::BlankLine.new
241-
242-
ucm.sort.each do |cm|
243-
body = report_class_module(cm) {
244-
[
245-
report_constants(cm),
246-
report_attributes(cm),
247-
report_methods(cm),
248-
].compact
249-
}
239+
if @coverage_level > 0 then
240+
calculate
250241

251-
report << body if body
242+
return GREAT_JOB_MESSAGE if @num_items == @doc_items
252243
end
253244

254-
if @coverage_level > 0 then
255-
calculate
245+
report = +""
246+
report << "The following items are not documented:\n\n"
256247

257-
return great_job if @num_items == @doc_items
248+
# Referenced-but-empty classes
249+
empty_classes.each do |cm|
250+
report << "#{cm.full_name} is referenced but empty.\n"
251+
report << "It probably came from another project. I'm sorry I'm holding it against you.\n\n"
258252
end
259253

260-
report
261-
end
254+
# Group items by file, then by type
255+
by_file = items.group_by { |item| item[:file] }
262256

263-
##
264-
# Returns a report on undocumented attributes in ClassModule +cm+
257+
by_file.sort_by { |file, _| file }.each do |file, file_items|
258+
report << "#{file}:\n"
265259

266-
def report_attributes(cm)
267-
return if cm.attributes.empty?
260+
by_type = file_items.group_by { |item| item[:type] }
268261

269-
report = []
262+
TYPE_ORDER.each do |type|
263+
next unless by_type[type]
264+
265+
report << " #{type}:\n"
266+
267+
sorted = by_type[type].sort_by { |item| [item[:line] || 0, item[:name]] }
268+
name_width = sorted.reduce(0) { |max, item| item[:line] && item[:name].length > max ? item[:name].length : max }
269+
270+
sorted.each do |item|
271+
if item[:line]
272+
report << " %-*s %s:%d\n" % [name_width, item[:name], item[:file], item[:line]]
273+
else
274+
report << " #{item[:name]}\n"
275+
end
276+
277+
if item[:undoc_params]
278+
report << " Undocumented params: #{item[:undoc_params].join(', ')}\n"
279+
end
280+
end
281+
end
270282

271-
cm.attributes.each do |attr|
272-
next if attr.documented?
273-
line = attr.line ? ":#{attr.line}" : nil
274-
report << " #{attr.definition} :#{attr.name} # in file #{attr.file.full_name}#{line}\n"
275283
report << "\n"
276284
end
277285

278286
report
279287
end
280288

281289
##
282-
# Returns a report on undocumented items in ClassModule +cm+
290+
# Collects all undocumented items across all classes and modules.
291+
# Returns [items, empty_classes] where items is an Array of Hashes
292+
# with keys :type, :name, :file, :line, and empty_classes is an
293+
# Array of ClassModule objects that are referenced but have no files.
283294

284-
def report_class_module(cm)
285-
return if cm.fully_documented? and @coverage_level.zero?
286-
return unless cm.display?
295+
def collect_undocumented_items
296+
empty_classes = []
297+
items = []
287298

288-
report = RDoc::Markup::Document.new
299+
@store.unique_classes_and_modules.each do |class_module|
300+
next unless class_module.display?
289301

290-
if cm.in_files.empty? then
291-
report << RDoc::Markup::Paragraph.new("#{cm.definition} is referenced but empty.")
292-
report << RDoc::Markup::Paragraph.new("It probably came from another project. I'm sorry I'm holding it against you.")
293-
294-
return report
295-
elsif cm.documented? then
296-
documented = true
297-
klass = RDoc::Markup::Verbatim.new("#{cm.definition} # is documented\n")
298-
else
299-
report << RDoc::Markup::Paragraph.new('In files:')
300-
301-
list = RDoc::Markup::List.new :BULLET
302-
303-
cm.in_files.each do |file|
304-
para = RDoc::Markup::Paragraph.new file.full_name
305-
list << RDoc::Markup::ListItem.new(nil, para)
302+
if class_module.in_files.empty?
303+
empty_classes << class_module
304+
next
306305
end
307306

308-
report << list
309-
report << RDoc::Markup::BlankLine.new
307+
unless class_module.documented? || class_module.full_name == 'Object'
308+
collect_undocumented_class_module(class_module, items)
309+
end
310310

311-
klass = RDoc::Markup::Verbatim.new("#{cm.definition}\n")
311+
collect_undocumented_constants(class_module, items)
312+
collect_undocumented_attributes(class_module, items)
313+
collect_undocumented_methods(class_module, items)
312314
end
313315

314-
klass << "\n"
315-
316-
body = yield.flatten # HACK remove #flatten
317-
318-
if body.empty? then
319-
return if documented
316+
[items, empty_classes]
317+
end
320318

321-
klass.parts.pop
322-
else
323-
klass.parts.concat body
319+
##
320+
# Collects undocumented classes or modules from +class_module+ into +items+.
321+
# Reopened classes/modules are reported in every file they appear in.
322+
323+
def collect_undocumented_class_module(class_module, items)
324+
class_module.in_files.map(&:full_name).uniq.each do |file|
325+
items << {
326+
type: class_module.type.capitalize,
327+
name: class_module.full_name,
328+
file: file,
329+
line: nil,
330+
}
324331
end
332+
end
325333

326-
klass << "end\n"
334+
##
335+
# Collects undocumented constants from +class_module+ into +items+.
327336

328-
report << klass
337+
def collect_undocumented_constants(class_module, items)
338+
class_module.constants.each do |constant|
339+
next unless constant.display?
340+
next if constant.documented? || constant.is_alias_for
329341

330-
report
342+
file = constant.file&.full_name
343+
next unless file
344+
345+
items << {
346+
type: "Constant",
347+
name: constant.full_name,
348+
file: file,
349+
line: constant.line,
350+
}
351+
end
331352
end
332353

333354
##
334-
# Returns a report on undocumented constants in ClassModule +cm+
335-
336-
def report_constants(cm)
337-
return if cm.constants.empty?
355+
# Collects undocumented attributes from +class_module+ into +items+.
338356

339-
report = []
357+
def collect_undocumented_attributes(class_module, items)
358+
class_module.attributes.each do |attr|
359+
next unless attr.display?
360+
next if attr.documented?
340361

341-
cm.constants.each do |constant|
342-
# TODO constant aliases are listed in the summary but not reported
343-
# figure out what to do here
344-
next if constant.documented? || constant.is_alias_for
362+
file = attr.file&.full_name
363+
next unless file
345364

346-
line = constant.line ? ":#{constant.line}" : line
347-
report << " # in file #{constant.file.full_name}#{line}\n"
348-
report << " #{constant.name} = nil\n"
349-
report << "\n"
365+
scope = attr.singleton ? "." : "#"
366+
items << {
367+
type: "Attribute",
368+
name: "#{class_module.full_name}#{scope}#{attr.name}",
369+
file: file,
370+
line: attr.line,
371+
}
350372
end
351-
352-
report
353373
end
354374

355375
##
356-
# Returns a report on undocumented methods in ClassModule +cm+
376+
# Collects undocumented methods from +class_module+ into +items+.
377+
# At coverage level > 0, also counts undocumented parameters.
357378

358-
def report_methods(cm)
359-
return if cm.method_list.empty?
379+
def collect_undocumented_methods(class_module, items)
380+
class_module.each_method do |method|
381+
next unless method.display?
382+
next if method.documented? && @coverage_level.zero?
360383

361-
report = []
384+
undoc_param_names = nil
362385

363-
cm.each_method do |method|
364-
next if method.documented? and @coverage_level.zero?
365-
366-
if @coverage_level > 0 then
386+
if @coverage_level > 0
367387
params, undoc = undoc_params method
368-
369388
@num_params += params
370389

371-
unless undoc.empty? then
390+
unless undoc.empty?
372391
@undoc_params += undoc.length
373-
374-
undoc = undoc.map do |param| "+#{param}+" end
375-
param_report = " # #{undoc.join ', '} is not documented\n"
392+
undoc_param_names = undoc
376393
end
377394
end
378395

379-
next if method.documented? and not param_report
396+
next if method.documented? && !undoc_param_names
380397

381-
line = method.line ? ":#{method.line}" : nil
382-
scope = method.singleton ? 'self.' : nil
398+
file = method.file&.full_name
399+
next unless file
383400

384-
report << " # in file #{method.file.full_name}#{line}\n"
385-
report << param_report if param_report
386-
report << " def #{scope}#{method.name}#{method.params}; end\n"
387-
report << "\n"
388-
end
401+
scope = method.singleton ? "." : "#"
402+
item = {
403+
type: "Method",
404+
name: "#{class_module.full_name}#{scope}#{method.name}",
405+
file: file,
406+
line: method.line,
407+
}
408+
item[:undoc_params] = undoc_param_names if undoc_param_names
389409

390-
report
410+
items << item
411+
end
391412
end
392413

393414
##
@@ -407,12 +428,10 @@ def summary
407428
@undoc_params,
408429
].max.to_s.length
409430

410-
report = RDoc::Markup::Verbatim.new
431+
report = +""
411432

412433
report << "Files: %*d\n" % [num_width, @num_files]
413-
414434
report << "\n"
415-
416435
report << "Classes: %*d (%*d undocumented)\n" % [
417436
num_width, @num_classes, undoc_width, @undoc_classes]
418437
report << "Modules: %*d (%*d undocumented)\n" % [
@@ -426,17 +445,14 @@ def summary
426445
report << "Parameters: %*d (%*d undocumented)\n" % [
427446
num_width, @num_params, undoc_width, @undoc_params] if
428447
@coverage_level > 0
429-
430448
report << "\n"
431-
432449
report << "Total: %*d (%*d undocumented)\n" % [
433450
num_width, @num_items, undoc_width, @undoc_items]
434-
435451
report << "%6.2f%% documented\n" % percent_doc
436452
report << "\n"
437453
report << "Elapsed: %0.1fs\n" % (Time.now - @start)
438454

439-
RDoc::Markup::Document.new report
455+
report
440456
end
441457

442458
##

0 commit comments

Comments
 (0)