Class: RDoc::Stats

Inherits:
Object
  • Object
show all
Includes:
Text
Defined in:
lib/rdoc/stats.rb,
lib/rdoc/stats/quiet.rb,
lib/rdoc/stats/normal.rb,
lib/rdoc/stats/verbose.rb

Overview

RDoc statistics collector which prints a summary and report of a project's documentation totals.

Defined Under Namespace

Classes: Normal, Quiet, Verbose

Constant Summary collapse

TYPE_ORDER =

Display order for item types in the coverage report

%w[Class Module Constant Attribute Method].freeze
GREAT_JOB_MESSAGE =

Message displayed when all items are documented

<<~MSG
  100% documentation!
  Great Job!
MSG

Constants included from Text

Text::MARKUP_FORMAT, Text::SPACE_SEPARATED_LETTER_CLASS

Instance Attribute Summary collapse

Attributes included from Text

#language

Instance Method Summary collapse

Methods included from Text

decode_legacy_label, expand_tabs, #flush_left, #markup, #normalize_comment, #parse, #snippet, #strip_hashes, #strip_newlines, #strip_stars, to_anchor, #wrap

Constructor Details

#initialize(store, num_files, verbosity = 1) ⇒ Stats

Creates a new Stats that will have num_files. verbosity defaults to 1 which will create an RDoc::Stats::Normal outputter.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/rdoc/stats.rb', line 43

def initialize(store, num_files, verbosity = 1)
  @num_files = num_files
  @store     = store

  @coverage_level   = 0
  @doc_items        = nil
  @files_so_far     = 0
  @fully_documented = false
  @num_params       = 0
  @percent_doc      = nil
  @start            = Time.now
  @undoc_params     = 0

  self.verbosity = verbosity
end

Instance Attribute Details

#coverage_level ⇒ Object

Output level for the coverage report



27
28
29
# File 'lib/rdoc/stats.rb', line 27

def coverage_level
  @coverage_level
end

#files_so_far ⇒ Object (readonly)

Count of files parsed during parsing



32
33
34
# File 'lib/rdoc/stats.rb', line 32

def files_so_far
  @files_so_far
end

#num_files ⇒ Object (readonly)

Total number of files found



37
38
39
# File 'lib/rdoc/stats.rb', line 37

def num_files
  @num_files
end

Instance Method Details

#add_alias(as) ⇒ Object

Records the parsing of an alias as.



73
74
75
# File 'lib/rdoc/stats.rb', line 73

def add_alias(as)
  @display.print_alias as
end

#add_attribute(attribute) ⇒ Object

Records the parsing of an attribute attribute



80
81
82
# File 'lib/rdoc/stats.rb', line 80

def add_attribute(attribute)
  @display.print_attribute attribute
end

#add_class(klass) ⇒ Object

Records the parsing of a class klass



87
88
89
# File 'lib/rdoc/stats.rb', line 87

def add_class(klass)
  @display.print_class klass
end

#add_constant(constant) ⇒ Object

Records the parsing of constant



94
95
96
# File 'lib/rdoc/stats.rb', line 94

def add_constant(constant)
  @display.print_constant constant
end

#add_file(file) ⇒ Object

Records the parsing of file



101
102
103
104
# File 'lib/rdoc/stats.rb', line 101

def add_file(file)
  @files_so_far += 1
  @display.print_file @files_so_far, file
end

#add_method(method) ⇒ Object

Records the parsing of method



109
110
111
# File 'lib/rdoc/stats.rb', line 109

def add_method(method)
  @display.print_method method
end

#add_module(mod) ⇒ Object

Records the parsing of a module mod



116
117
118
# File 'lib/rdoc/stats.rb', line 116

def add_module(mod)
  @display.print_module mod
end

#begin_adding ⇒ Object

Call this to mark the beginning of parsing for display purposes



123
124
125
# File 'lib/rdoc/stats.rb', line 123

def begin_adding
  @display.begin_adding
end

#calculate ⇒ Object

Calculates documentation totals and percentages for classes, modules, constants, attributes and methods.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/rdoc/stats.rb', line 131

def calculate
  return if @doc_items

  ucm = @store.unique_classes_and_modules

  classes = @store.unique_classes.reject { |cm| cm.full_name == 'Object' }

  constants = []
  ucm.each { |cm| constants.concat cm.constants }

  methods = []
  ucm.each { |cm| methods.concat cm.method_list }

  attributes = []
  ucm.each { |cm| attributes.concat cm.attributes }

  @num_attributes, @undoc_attributes = doc_stats attributes
  @num_classes,    @undoc_classes    = doc_stats classes
  @num_constants,  @undoc_constants  = doc_stats constants
  @num_methods,    @undoc_methods    = doc_stats methods
  @num_modules,    @undoc_modules    = doc_stats @store.unique_modules

  @num_items =
    @num_attributes +
    @num_classes +
    @num_constants +
    @num_methods +
    @num_modules +
    @num_params

  @undoc_items =
    @undoc_attributes +
    @undoc_classes +
    @undoc_constants +
    @undoc_methods +
    @undoc_modules +
    @undoc_params

  @doc_items = @num_items - @undoc_items
end

#collect_undocumented_attributes(class_module, items) ⇒ Object

Collects undocumented attributes from class_module into items.



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/rdoc/stats.rb', line 358

def collect_undocumented_attributes(class_module, items)
  class_module.attributes.each do |attr|
    next unless attr.display?
    next if attr.documented?

    file = attr.file&.full_name
    next unless file

    scope = attr.singleton ? "." : "#"
    items << {
      type: "Attribute",
      name: "#{class_module.full_name}#{scope}#{attr.name}",
      file: file,
      line: attr.line,
    }
  end
end

#collect_undocumented_class_module(class_module, items) ⇒ Object

Collects undocumented classes or modules from class_module into items. Reopened classes/modules are reported in every file they appear in.



324
325
326
327
328
329
330
331
332
333
# File 'lib/rdoc/stats.rb', line 324

def collect_undocumented_class_module(class_module, items)
  class_module.in_files.map(&:full_name).uniq.each do |file|
    items << {
      type: class_module.type.capitalize,
      name: class_module.full_name,
      file: file,
      line: nil,
    }
  end
end

#collect_undocumented_constants(class_module, items) ⇒ Object

Collects undocumented constants from class_module into items.



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/rdoc/stats.rb', line 338

def collect_undocumented_constants(class_module, items)
  class_module.constants.each do |constant|
    next unless constant.display?
    next if constant.documented? || constant.is_alias_for

    file = constant.file&.full_name
    next unless file

    items << {
      type: "Constant",
      name: constant.full_name,
      file: file,
      line: constant.line,
    }
  end
end

#collect_undocumented_items ⇒ Object

Collects all undocumented items across all classes and modules. Returns [items, empty_classes] where items is an Array of Hashes with keys :type, :name, :file, :line, and empty_classes is an Array of ClassModule objects that are referenced but have no files.



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/rdoc/stats.rb', line 296

def collect_undocumented_items
  empty_classes = []
  items = []

  @store.unique_classes_and_modules.each do |class_module|
    next unless class_module.display?

    if class_module.in_files.empty?
      empty_classes << class_module
      next
    end

    unless class_module.documented? || class_module.full_name == 'Object'
      collect_undocumented_class_module(class_module, items)
    end

    collect_undocumented_constants(class_module, items)
    collect_undocumented_attributes(class_module, items)
    collect_undocumented_methods(class_module, items)
  end

  [items, empty_classes]
end

#collect_undocumented_methods(class_module, items) ⇒ Object

Collects undocumented methods from class_module into items. At coverage level > 0, also counts undocumented parameters.



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/rdoc/stats.rb', line 380

def collect_undocumented_methods(class_module, items)
  class_module.each_method do |method|
    next unless method.display?
    next if method.documented? && @coverage_level.zero?

    undoc_param_names = nil

    if @coverage_level > 0
      params, undoc = undoc_params method
      @num_params += params

      unless undoc.empty?
        @undoc_params += undoc.length
        undoc_param_names = undoc
      end
    end

    next if method.documented? && !undoc_param_names

    file = method.file&.full_name
    next unless file

    scope = method.singleton ? "." : "#"
    item = {
      type: "Method",
      name: "#{class_module.full_name}#{scope}#{method.name}",
      file: file,
      line: method.line,
    }
    item[:undoc_params] = undoc_param_names if undoc_param_names

    items << item
  end
end

#doc_stats(collection) ⇒ Object

Returns the length and number of undocumented items in collection.



188
189
190
191
# File 'lib/rdoc/stats.rb', line 188

def doc_stats(collection)
  visible = collection.select { |item| item.display? }
  [visible.length, visible.count { |item| not item.documented? }]
end

#done_adding ⇒ Object

Call this to mark the end of parsing for display purposes



196
197
198
# File 'lib/rdoc/stats.rb', line 196

def done_adding
  @display.done_adding
end

#fully_documented? ⇒ Boolean

The documentation status of this project. true when 100%, false when less than 100% and nil when unknown.

Set by calling #calculate

Returns:

  • (Boolean)


206
207
208
# File 'lib/rdoc/stats.rb', line 206

def fully_documented?
  @fully_documented
end

#percent_doc ⇒ Object

Calculates the percentage of items documented.



213
214
215
216
217
218
219
220
221
222
# File 'lib/rdoc/stats.rb', line 213

def percent_doc
  return @percent_doc if @percent_doc

  @fully_documented = (@num_items - @doc_items) == 0

  @percent_doc = @doc_items.to_f / @num_items * 100 if @num_items.nonzero?
  @percent_doc ||= 0

  @percent_doc
end

#report ⇒ Object

Returns a report on which items are not documented



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/rdoc/stats.rb', line 227

def report
  if @coverage_level > 0
    extend Text
  end

  if @coverage_level.zero?
    calculate

    return GREAT_JOB_MESSAGE if @num_items == @doc_items
  end

  items, empty_classes = collect_undocumented_items

  if @coverage_level > 0
    calculate

    return GREAT_JOB_MESSAGE if @num_items == @doc_items
  end

  report = +""
  report << "The following items are not documented:\n\n"

  # Referenced-but-empty classes
  empty_classes.each do |cm|
    report << "#{cm.full_name} is referenced but empty.\n"
    report << "It probably came from another project.  I'm sorry I'm holding it against you.\n\n"
  end

  # Group items by file, then by type
  by_file = items.group_by { |item| item[:file] }

  by_file.sort_by { |file, _| file }.each do |file, file_items|
    report << "#{file}:\n"

    by_type = file_items.group_by { |item| item[:type] }

    TYPE_ORDER.each do |type|
      next unless by_type[type]

      report << "  #{type}:\n"

      sorted = by_type[type].sort_by { |item| [item[:line] || 0, item[:name]] }
      name_width = sorted.reduce(0) { |max, item| item[:line] && item[:name].length > max ? item[:name].length : max }

      sorted.each do |item|
        if item[:line]
          report << "    %-*s %s:%d\n" % [name_width, item[:name], item[:file], item[:line]]
        else
          report << "    #{item[:name]}\n"
        end

        if item[:undoc_params]
          report << "      Undocumented params: #{item[:undoc_params].join(', ')}\n"
        end
      end
    end

    report << "\n"
  end

  report
end

#summary ⇒ Object

Returns a summary of the collected statistics.



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/rdoc/stats.rb', line 418

def summary
  calculate

  num_width = [@num_files, @num_items].max.to_s.length
  undoc_width = [
    @undoc_attributes,
    @undoc_classes,
    @undoc_constants,
    @undoc_items,
    @undoc_methods,
    @undoc_modules,
    @undoc_params,
  ].max.to_s.length

  report = +""

  report << "Files:      %*d\n" % [num_width, @num_files]
  report << "\n"
  report << "Classes:    %*d (%*d undocumented)\n" % [
    num_width, @num_classes, undoc_width, @undoc_classes]
  report << "Modules:    %*d (%*d undocumented)\n" % [
    num_width, @num_modules, undoc_width, @undoc_modules]
  report << "Constants:  %*d (%*d undocumented)\n" % [
    num_width, @num_constants, undoc_width, @undoc_constants]
  report << "Attributes: %*d (%*d undocumented)\n" % [
    num_width, @num_attributes, undoc_width, @undoc_attributes]
  report << "Methods:    %*d (%*d undocumented)\n" % [
    num_width, @num_methods, undoc_width, @undoc_methods]
  report << "Parameters: %*d (%*d undocumented)\n" % [
    num_width, @num_params, undoc_width, @undoc_params] if
      @coverage_level > 0
  report << "\n"
  report << "Total:      %*d (%*d undocumented)\n" % [
    num_width, @num_items, undoc_width, @undoc_items]
  report << "%6.2f%% documented\n" % percent_doc
  report << "\n"
  report << "Elapsed: %0.1fs\n" % (Time.now - @start)

  report
end

#undoc_params(method) ⇒ Object

Determines which parameters in method were not documented. Returns a total parameter count and an Array of undocumented methods.



463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'lib/rdoc/stats.rb', line 463

def undoc_params(method)
  @formatter ||= Markup::ToTtOnly.new

  params = method.param_list

  params = params.map { |param| param.gsub(/^\*\*?/, '') }

  return 0, [] if params.empty?

  document = parse method.comment

  tts = document.accept @formatter

  undoc = params - tts

  [params.length, undoc]
end

#verbosity=(verbosity) ⇒ Object

Sets the verbosity level, rebuilding the display outputter.



62
63
64
65
66
67
68
# File 'lib/rdoc/stats.rb', line 62

def verbosity=(verbosity)
  @display = case verbosity
             when 0 then Quiet.new   @num_files
             when 1 then Normal.new  @num_files
             else        Verbose.new @num_files
             end
end