Skip to content

Commit 4d7546c

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 4d7546c

File tree

3 files changed

+396
-335
lines changed

3 files changed

+396
-335
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: 127 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ class RDoc::Stats
77

88
include RDoc::Text
99

10+
TYPE_ORDER = %w[Class Module Constant Attribute Method].freeze
11+
GREAT_JOB_MESSAGE = <<~MSG
12+
100% documentation!
13+
Great Job!
14+
MSG
15+
1016
##
1117
# Output level for the coverage report
1218

@@ -193,18 +199,6 @@ def fully_documented?
193199
@fully_documented
194200
end
195201

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-
208202
##
209203
# Calculates the percentage of items documented.
210204

@@ -230,164 +224,181 @@ def report
230224
if @coverage_level.zero? then
231225
calculate
232226

233-
return great_job if @num_items == @doc_items
227+
return GREAT_JOB_MESSAGE if @num_items == @doc_items
234228
end
235229

236-
ucm = @store.unique_classes_and_modules
230+
items, empty_classes = collect_undocumented_items
237231

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-
}
232+
if @coverage_level > 0 then
233+
calculate
250234

251-
report << body if body
235+
return GREAT_JOB_MESSAGE if @num_items == @doc_items
252236
end
253237

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

257-
return great_job if @num_items == @doc_items
241+
# Referenced-but-empty classes
242+
empty_classes.each do |cm|
243+
report << "#{cm.full_name} is referenced but empty.\n"
244+
report << "It probably came from another project. I'm sorry I'm holding it against you.\n\n"
258245
end
259246

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

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

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

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

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"
275276
report << "\n"
276277
end
277278

278279
report
279280
end
280281

281282
##
282-
# Returns a report on undocumented items in ClassModule +cm+
283+
# Collects all undocumented items across all classes and modules.
284+
# Returns [items, empty_classes] where items is an Array of Hashes
285+
# with keys :type, :name, :file, :line, and empty_classes is an
286+
# Array of ClassModule objects that are referenced but have no files.
283287

284-
def report_class_module(cm)
285-
return if cm.fully_documented? and @coverage_level.zero?
286-
return unless cm.display?
288+
def collect_undocumented_items
289+
empty_classes = []
290+
items = []
287291

288-
report = RDoc::Markup::Document.new
292+
@store.unique_classes_and_modules.each do |class_module|
293+
next unless class_module.display?
289294

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)
295+
if class_module.in_files.empty?
296+
empty_classes << class_module
297+
next
306298
end
307299

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

311-
klass = RDoc::Markup::Verbatim.new("#{cm.definition}\n")
304+
collect_undocumented_constants(class_module, items)
305+
collect_undocumented_attributes(class_module, items)
306+
collect_undocumented_methods(class_module, items)
312307
end
313308

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

321-
klass.parts.pop
322-
else
323-
klass.parts.concat body
312+
##
313+
# Collects undocumented classes or modules from +class_module+ into +items+.
314+
# Reopened classes/modules are reported in every file they appear in.
315+
316+
def collect_undocumented_class_module(class_module, items)
317+
class_module.in_files.map(&:full_name).uniq.each do |file|
318+
items << {
319+
type: class_module.type.capitalize,
320+
name: class_module.full_name,
321+
file: file,
322+
line: nil,
323+
}
324324
end
325+
end
326+
327+
##
328+
# Collects undocumented constants from +class_module+ into +items+.
325329

326-
klass << "end\n"
330+
def collect_undocumented_constants(class_module, items)
331+
class_module.constants.each do |constant|
332+
next if constant.documented? || constant.is_alias_for
327333

328-
report << klass
334+
file = constant.file&.full_name
335+
next unless file
329336

330-
report
337+
items << {
338+
type: "Constant",
339+
name: constant.full_name,
340+
file: file,
341+
line: constant.line,
342+
}
343+
end
331344
end
332345

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

339-
report = []
349+
def collect_undocumented_attributes(class_module, items)
350+
class_module.attributes.each do |attr|
351+
next if attr.documented?
340352

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
353+
file = attr.file&.full_name
354+
next unless file
345355

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"
356+
scope = attr.singleton ? "." : "#"
357+
items << {
358+
type: "Attribute",
359+
name: "#{class_module.full_name}#{scope}#{attr.name}",
360+
file: file,
361+
line: attr.line,
362+
}
350363
end
351-
352-
report
353364
end
354365

355366
##
356-
# Returns a report on undocumented methods in ClassModule +cm+
357-
358-
def report_methods(cm)
359-
return if cm.method_list.empty?
367+
# Collects undocumented methods from +class_module+ into +items+.
368+
# At coverage level > 0, also counts undocumented parameters.
360369

361-
report = []
370+
def collect_undocumented_methods(class_module, items)
371+
class_module.each_method do |method|
372+
next if method.documented? && @coverage_level.zero?
362373

363-
cm.each_method do |method|
364-
next if method.documented? and @coverage_level.zero?
374+
undoc_param_names = nil
365375

366-
if @coverage_level > 0 then
376+
if @coverage_level > 0
367377
params, undoc = undoc_params method
368-
369378
@num_params += params
370379

371-
unless undoc.empty? then
380+
unless undoc.empty?
372381
@undoc_params += undoc.length
373-
374-
undoc = undoc.map do |param| "+#{param}+" end
375-
param_report = " # #{undoc.join ', '} is not documented\n"
382+
undoc_param_names = undoc
376383
end
377384
end
378385

379-
next if method.documented? and not param_report
386+
next if method.documented? && !undoc_param_names
380387

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

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
391+
scope = method.singleton ? "." : "#"
392+
item = {
393+
type: "Method",
394+
name: "#{class_module.full_name}#{scope}#{method.name}",
395+
file: file,
396+
line: method.line,
397+
}
398+
item[:undoc_params] = undoc_param_names if undoc_param_names
389399

390-
report
400+
items << item
401+
end
391402
end
392403

393404
##
@@ -407,12 +418,10 @@ def summary
407418
@undoc_params,
408419
].max.to_s.length
409420

410-
report = RDoc::Markup::Verbatim.new
421+
report = +""
411422

412423
report << "Files: %*d\n" % [num_width, @num_files]
413-
414424
report << "\n"
415-
416425
report << "Classes: %*d (%*d undocumented)\n" % [
417426
num_width, @num_classes, undoc_width, @undoc_classes]
418427
report << "Modules: %*d (%*d undocumented)\n" % [
@@ -426,17 +435,14 @@ def summary
426435
report << "Parameters: %*d (%*d undocumented)\n" % [
427436
num_width, @num_params, undoc_width, @undoc_params] if
428437
@coverage_level > 0
429-
430438
report << "\n"
431-
432439
report << "Total: %*d (%*d undocumented)\n" % [
433440
num_width, @num_items, undoc_width, @undoc_items]
434-
435441
report << "%6.2f%% documented\n" % percent_doc
436442
report << "\n"
437443
report << "Elapsed: %0.1fs\n" % (Time.now - @start)
438444

439-
RDoc::Markup::Document.new report
445+
report
440446
end
441447

442448
##

0 commit comments

Comments
 (0)