@@ -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