inch 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +170 -56
  3. data/TODOS.md +0 -6
  4. data/config/defaults.rb +82 -0
  5. data/lib/inch/cli/command/base.rb +9 -0
  6. data/lib/inch/cli/command/base_list.rb +8 -8
  7. data/lib/inch/cli/command/base_object.rb +1 -1
  8. data/lib/inch/cli/command/list.rb +6 -1
  9. data/lib/inch/cli/command/options/base.rb +1 -1
  10. data/lib/inch/cli/command/options/suggest.rb +3 -1
  11. data/lib/inch/cli/command/output/list.rb +6 -6
  12. data/lib/inch/cli/command/output/show.rb +3 -3
  13. data/lib/inch/cli/command/output/stats.rb +52 -33
  14. data/lib/inch/cli/command/output/suggest.rb +7 -7
  15. data/lib/inch/cli/command/show.rb +5 -0
  16. data/lib/inch/cli/command/stats.rb +6 -1
  17. data/lib/inch/cli/command/suggest.rb +31 -12
  18. data/lib/inch/cli/command.rb +7 -14
  19. data/lib/inch/cli/command_parser.rb +2 -9
  20. data/lib/inch/cli/sparkline_helper.rb +13 -13
  21. data/lib/inch/cli/weighted_list.rb +85 -0
  22. data/lib/inch/cli.rb +2 -1
  23. data/lib/inch/code_object/nodoc_helper.rb +2 -0
  24. data/lib/inch/code_object/proxy/base.rb +36 -13
  25. data/lib/inch/code_object/proxy/method_object.rb +29 -0
  26. data/lib/inch/code_object/proxy/namespace_object.rb +6 -0
  27. data/lib/inch/config.rb +30 -37
  28. data/lib/inch/evaluation/base.rb +53 -1
  29. data/lib/inch/evaluation/constant_object.rb +8 -11
  30. data/lib/inch/evaluation/file.rb +1 -1
  31. data/lib/inch/evaluation/grade.rb +41 -0
  32. data/lib/inch/evaluation/grade_list.rb +32 -0
  33. data/lib/inch/evaluation/method_object.rb +10 -51
  34. data/lib/inch/evaluation/namespace_object.rb +8 -49
  35. data/lib/inch/evaluation/{criteria.rb → object_schema.rb} +12 -22
  36. data/lib/inch/evaluation/read_write_methods.rb +21 -0
  37. data/lib/inch/evaluation/role/method.rb +8 -0
  38. data/lib/inch/evaluation.rb +5 -2
  39. data/lib/inch/version.rb +1 -1
  40. data/lib/inch.rb +3 -1
  41. data/test/fixtures/simple/lib/broken.rb +15 -0
  42. data/test/fixtures/simple/lib/role_methods.rb +15 -0
  43. data/test/inch/cli/weighted_list_test.rb +55 -0
  44. data/test/inch/code_object/proxy/method_object_test.rb +42 -0
  45. data/test/integration/stats_options_test.rb +1 -1
  46. metadata +11 -5
  47. data/lib/inch/evaluation/score_range.rb +0 -38
@@ -16,10 +16,10 @@ module Inch
16
16
  [203, 203, 204, 204, 205, 206, 207]
17
17
  ].flatten.map { |s| :"color#{s}" }
18
18
 
19
- def initialize(options, objects, ranges)
19
+ def initialize(options, objects, grade_lists)
20
20
  @options = options
21
21
  @objects = objects
22
- @ranges = ranges
22
+ @grade_lists = grade_lists
23
23
 
24
24
  method("display_#{@options.format}").call
25
25
  end
@@ -27,19 +27,23 @@ module Inch
27
27
  private
28
28
 
29
29
  def display_text
30
- display_text_grades
31
- display_text_priorities
30
+ print_grades
31
+ print_grades_by_priority
32
+ print_priorities
32
33
  puts
33
34
  puts 'Try `--format json|yaml` for raw numbers.'.dark
34
35
  end
35
36
 
36
- def display_text_grades
37
- sparkline = ranges_sparkline(@ranges).to_s(' ')
37
+ def print_grades
38
+ sparkline = grade_lists_sparkline(@grade_lists).to_s(' ')
38
39
  puts
39
40
  puts 'Grade distribution: (undocumented, C, B, A)'
40
41
  puts
41
42
  puts " Overall: #{sparkline} #{objects.size.to_s.rjust(5)} objects"
42
43
  puts
44
+ end
45
+
46
+ def print_grades_by_priority
43
47
  puts 'Grade distribution by priority:'
44
48
  puts
45
49
  PRIORITY_MAP.each do |priority_range, arrow|
@@ -51,25 +55,29 @@ module Inch
51
55
  end
52
56
  end
53
57
 
54
- def display_text_priorities
58
+ def print_grade_list(grade_list)
59
+ list = grade_list.objects.map(&:priority)
60
+
61
+ priorities = {}
62
+ (-7..7).each do |key|
63
+ priorities[key.to_s] = list.select { |p| p == key }.size
64
+ end
65
+
66
+ sparkline = Sparkr::Sparkline.new(priorities.values)
67
+ sparkline.format do |tick, count, index|
68
+ tick.color( PRIORITY_COLORS[index] )
69
+ end
70
+ puts " #{grade_list.grade}: " + sparkline.to_s(' ') +
71
+ " #{grade_list.objects.size.to_s.rjust(5)} objects"
72
+ puts
73
+ end
74
+
75
+ def print_priorities
55
76
  puts "Priority distribution in grades: (low to high)"
56
77
  puts
57
78
  puts " #{PRIORITY_MAP.values.reverse.join(' ')}"
58
- @ranges.reverse.each do |range|
59
- list = range.objects.map(&:priority)
60
-
61
- priorities = {}
62
- (-7..7).each do |key|
63
- priorities[key.to_s] = list.select { |p| p == key }.size
64
- end
65
-
66
- sparkline = Sparkr::Sparkline.new(priorities.values)
67
- sparkline.format do |tick, count, index|
68
- tick.color( PRIORITY_COLORS[index] )
69
- end
70
- puts " #{range.grade}: " + sparkline.to_s(' ') +
71
- " #{range.objects.size.to_s.rjust(5)} objects"
72
- puts
79
+ @grade_lists.reverse.each do |grade_list|
80
+ print_grade_list(grade_list)
73
81
  end
74
82
  end
75
83
 
@@ -82,25 +90,36 @@ module Inch
82
90
  end
83
91
 
84
92
  def stats_hash
85
- hash = {}
93
+ {
94
+ 'grade_lists' => __grade_lists,
95
+ 'scores' => __scores,
96
+ 'priorities' => __priorities
97
+ }
98
+ end
86
99
 
87
- hash['ranges'] = {}
88
- @ranges.each do |r|
89
- hash['ranges'][r.grade.to_s] = r.objects.size
100
+ def __grade_lists
101
+ hash = {}
102
+ @grade_lists.each do |r|
103
+ hash[r.grade.to_s] = r.objects.size
90
104
  end
105
+ hash
106
+ end
91
107
 
92
- hash['scores'] = {}
108
+ def __scores
109
+ hash = {}
93
110
  @objects.sort_by(&:score).each do |o|
94
- hash['scores'][o.score.to_i] ||= 0
95
- hash['scores'][o.score.to_i] += 1
111
+ hash[o.score.to_i] ||= 0
112
+ hash[o.score.to_i] += 1
96
113
  end
114
+ hash
115
+ end
97
116
 
98
- hash['priorities'] = {}
117
+ def __priorities
118
+ hash = {}
99
119
  @objects.sort_by(&:priority).each do |o|
100
- hash['priorities'][o.priority.to_i] ||= 0
101
- hash['priorities'][o.priority.to_i] += 1
120
+ hash[o.priority.to_i] ||= 0
121
+ hash[o.priority.to_i] += 1
102
122
  end
103
-
104
123
  hash
105
124
  end
106
125
 
@@ -20,13 +20,13 @@ module Inch
20
20
  # @param options [Command::Options::Suggest]
21
21
  # @param objects_to_display [Array<CodeObject::Proxy::Base>]
22
22
  # @param relevant_objects [Array<CodeObject::Proxy::Base>] the objects meeting the criteria defined in +options+
23
- # @param ranges [Array<Evaluation::ScoreRange>]
23
+ # @param grade_lists [Array<Evaluation::GradeList>]
24
24
  # @param files [Array<Evaluation::File>]
25
- def initialize(options, objects_to_display, relevant_objects, ranges, files)
25
+ def initialize(options, objects_to_display, relevant_objects, grade_lists, files)
26
26
  @options = options
27
27
  @objects = objects_to_display
28
28
  @relevant_objects = relevant_objects
29
- @ranges = ranges
29
+ @grade_lists = grade_lists
30
30
  @files = files
31
31
 
32
32
  if objects.empty?
@@ -70,11 +70,11 @@ module Inch
70
70
 
71
71
  def display_list
72
72
  @options.grades_to_display.map do |grade|
73
- r = range(grade)
73
+ r = grade_list(grade)
74
74
  grade_objects = objects.select { |o| o.grade == r.grade }
75
75
  unless grade_objects.empty?
76
76
  trace
77
- trace_header(RANGE_LABELS[r.grade], r.color, r.bg_color)
77
+ trace_header(RANGE_LABELS[r.grade.to_sym], r.color, r.bg_color)
78
78
  grade_objects.each do |o|
79
79
  grade = o.grade.to_s.ljust(2).color(r.color)
80
80
  priority = o.priority
@@ -94,8 +94,8 @@ module Inch
94
94
  end.compact
95
95
  end
96
96
 
97
- def range(grade)
98
- @ranges.detect { |r| r.grade == grade }
97
+ def grade_list(grade_symbol)
98
+ @grade_lists.detect { |r| r.grade.to_sym == grade_symbol }
99
99
  end
100
100
  end
101
101
  end
@@ -1,7 +1,12 @@
1
+ require_relative 'options/show'
2
+ require_relative 'output/show'
3
+
1
4
  module Inch
2
5
  module CLI
3
6
  module Command
4
7
  class Show < BaseObject
8
+ register_command_as :show
9
+
5
10
  def description
6
11
  'Shows an object with its results'
7
12
  end
@@ -1,7 +1,12 @@
1
+ require_relative 'options/stats'
2
+ require_relative 'output/stats'
3
+
1
4
  module Inch
2
5
  module CLI
3
6
  module Command
4
7
  class Stats < List
8
+ register_command_as :stats
9
+
5
10
  def description
6
11
  'Lists all objects with their results'
7
12
  end
@@ -12,7 +17,7 @@ module Inch
12
17
 
13
18
  def run(*args)
14
19
  prepare_list(*args)
15
- Output::Stats.new(@options, objects, @ranges)
20
+ Output::Stats.new(@options, objects, @grade_lists)
16
21
  end
17
22
  end
18
23
  end
@@ -1,7 +1,12 @@
1
+ require_relative 'options/suggest'
2
+ require_relative 'output/suggest'
3
+
1
4
  module Inch
2
5
  module CLI
3
6
  module Command
4
7
  class Suggest < List
8
+ register_command_as :suggest, true
9
+
5
10
  def description
6
11
  'Suggests some objects to be doucmented (better)'
7
12
  end
@@ -17,29 +22,43 @@ module Inch
17
22
  # @return [void]
18
23
  def run(*args)
19
24
  prepare_list(*args)
20
- Output::Suggest.new(@options, objects_to_display, relevant_objects, @ranges, files)
25
+ Output::Suggest.new(@options, objects_to_display, relevant_objects, @grade_lists, files)
26
+ end
27
+
28
+ private
29
+
30
+ # @return [Fixnum] how many objects should be displayed in the output
31
+ def object_count
32
+ @options.object_count
33
+ end
34
+
35
+ # @return [Array<Fixnum>]
36
+ # how many objects of each grade should be displayed in the output
37
+ def object_list_counts
38
+ @options.grade_weights.map { |w| w * object_count }
21
39
  end
22
40
 
41
+ # @return [Array] the objects that should be displayed in the output
23
42
  def objects_to_display
24
43
  @objects_to_display ||= filter_objects_to_display
25
44
  end
26
45
 
46
+ # @return [Array] the objects that should be displayed in the output
27
47
  def filter_objects_to_display
28
- list = []
48
+ grade_list = []
29
49
  @options.grades_to_display.map do |grade|
30
- r = range(grade)
50
+ r = grade_list(grade)
31
51
  arr = select_by_priority(r.objects, @options.object_min_priority)
32
52
  arr = arr.select { |o| o.score <= @options.object_max_score }
33
- list.concat arr
53
+ grade_list << arr
34
54
  end
35
55
 
36
- list = sort_by_priority(list)
56
+ weighted_list = WeightedList.new(grade_list, object_list_counts)
57
+
58
+ list = sort_by_priority(weighted_list.to_a.flatten)
37
59
 
38
- if list.size > @options.object_count
39
- list = list[0...@options.object_count]
40
- elsif list.size < @options.object_count
41
- # should we add objects with lower priority to fill out the
42
- # requested count?
60
+ if list.size > object_count
61
+ list = list[0...object_count]
43
62
  end
44
63
  list
45
64
  end
@@ -78,8 +97,8 @@ module Inch
78
97
  objects.map(&:grade).uniq
79
98
  end
80
99
 
81
- def range(grade)
82
- @ranges.detect { |r| r.grade == grade }
100
+ def grade_list(grade_symbol)
101
+ @grade_lists.detect { |r| r.grade.to_sym == grade_symbol }
83
102
  end
84
103
 
85
104
  def relevant_objects
@@ -2,22 +2,15 @@ require_relative 'command/base'
2
2
  require_relative 'command/base_list'
3
3
  require_relative 'command/base_object'
4
4
 
5
- require_relative 'command/list'
6
- require_relative 'command/show'
7
- require_relative 'command/stats'
8
- require_relative 'command/suggest'
9
-
10
5
  require_relative 'command/options/base'
11
6
  require_relative 'command/options/base_list'
12
7
  require_relative 'command/options/base_object'
13
8
 
14
- require_relative 'command/options/list'
15
- require_relative 'command/options/show'
16
- require_relative 'command/options/stats'
17
- require_relative 'command/options/suggest'
18
-
19
9
  require_relative 'command/output/base'
20
- require_relative 'command/output/list'
21
- require_relative 'command/output/show'
22
- require_relative 'command/output/stats'
23
- require_relative 'command/output/suggest'
10
+
11
+ require_relative 'command_parser'
12
+
13
+ require_relative 'command/list'
14
+ require_relative 'command/show'
15
+ require_relative 'command/stats'
16
+ require_relative 'command/suggest'
@@ -43,15 +43,8 @@ module Inch
43
43
  attr_accessor :default_command
44
44
  end
45
45
 
46
- self.commands = {
47
- :list => Command::List,
48
- :show => Command::Show,
49
- :stats => Command::Stats,
50
- :suggest => Command::Suggest,
51
- }
52
-
53
- self.default_command = :suggest
54
-
46
+ self.commands = {}
47
+
55
48
  # Convenience method to create a new CommandParser and call {#run}
56
49
  # @return (see #run)
57
50
  def self.run(*args)
@@ -1,28 +1,28 @@
1
1
  module Inch
2
2
  module CLI
3
3
  module SparklineHelper
4
- def ranges_sparkline(_ranges)
5
- ranges = _ranges.reverse
6
- list = ranges.map { |r| r.objects.size }
7
- sparkline = Sparkr::Sparkline.new(list)
8
- sparkline.format do |tick, count, index|
9
- t = tick.color(ranges[index].color)
10
- index == 0 ? t + ' ' : t
11
- end
4
+ def grade_lists_sparkline(_grade_lists)
5
+ grade_lists = _grade_lists.reverse
6
+ list = grade_lists.map { |r| r.objects.size }
7
+ __sparkline(list, grade_lists)
12
8
  end
13
9
 
14
10
  def grades_sparkline(objects)
15
11
  grades = {}
16
12
  objects.each do |o|
17
- grades[o.grade] ||= 0
18
- grades[o.grade] += 1
13
+ grades[o.grade.to_sym] ||= 0
14
+ grades[o.grade.to_sym] += 1
19
15
  end
20
- ranges = Evaluation.new_score_ranges.reverse
21
- order = ranges.map(&:grade)
16
+ grade_lists = Evaluation.new_grade_lists.reverse
17
+ order = grade_lists.map(&:to_sym)
22
18
  list = order.map { |g| grades[g] }
19
+ __sparkline(list, grade_lists)
20
+ end
21
+
22
+ def __sparkline(list, grade_lists)
23
23
  sparkline = Sparkr::Sparkline.new(list)
24
24
  sparkline.format do |tick, count, index|
25
- t = tick.color(ranges[index].color)
25
+ t = tick.color(grade_lists[index].color)
26
26
  index == 0 ? t + ' ' : t
27
27
  end
28
28
  end
@@ -0,0 +1,85 @@
1
+ module Inch
2
+ module CLI
3
+ class WeightedList
4
+ # Trims down a list of object lists to given sizes.
5
+ # If there are not enough objects in any particular tier,
6
+ # they are filled up from the tier below/above.
7
+ #
8
+ # @example
9
+ #
10
+ # list = [
11
+ # [:B1, :B2],
12
+ # [:C1, :C2, :C3, :C4, :C5, :C6, :C7, :C8, :C9, :C10],
13
+ # [:U1, :U2, :U3, :U4, :U5, :U6, :U7, :U8, :U9, :U10]
14
+ # ]
15
+ # counts = [4, 8, 8]
16
+ #
17
+ # This means there should be 4 +:B+s, 8 +:C+s, and 8 +:U+s in the
18
+ # resulting list. But we only have 2 +:B+s, so the result is filled up
19
+ # with +:U+s:
20
+ #
21
+ # WeightedList.new(list, counts).to_a
22
+ # # => [
23
+ # [:B1, :B2],
24
+ # [:C1, :C2, :C3, :C4, :C5, :C6, :C7, :C8],
25
+ # [:U1, :U2, :U3, :U4, :U5, :U6, :U7, :U8, :U9, :U10]
26
+ # ]
27
+ #
28
+ # @param list_of_lists [Array<Array>]
29
+ # @param counts [Array<Fixnum>]
30
+ def initialize(list_of_lists, counts)
31
+ @original = list_of_lists
32
+ @max_counts = counts
33
+ compute_list
34
+ end
35
+
36
+ # @return [Array]
37
+ def to_a
38
+ @list
39
+ end
40
+
41
+ private
42
+
43
+ def compute_list
44
+ @list = init_empty_list(@original.size)
45
+ still_missing = fill_list_with_predefined_sizes
46
+
47
+ # all sublists are now filled up to their max capacity but we might
48
+ # have missed some objects (+still_missing+), because there weren't
49
+ # enough
50
+
51
+ @original.reverse_each.with_index do |sublist, rear_index|
52
+ if still_missing > 0
53
+ index = @original.size - rear_index - 1
54
+ present_count = @max_counts[index]
55
+ not_yet_in_list = sublist[present_count..-1]
56
+ if not_yet_in_list
57
+ put_in_list = not_yet_in_list[0...still_missing]
58
+ @list[index].concat put_in_list
59
+ still_missing -= put_in_list.size
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def init_empty_list(sublist_count)
66
+ list = []
67
+ sublist_count.times { list << [] }
68
+ list
69
+ end
70
+
71
+ def fill_list_with_predefined_sizes
72
+ missing = 0
73
+ @original.each_with_index do |sublist, index|
74
+ target_count = @max_counts[index]
75
+ @list[index].concat sublist[0...target_count]
76
+
77
+ if sublist.size < target_count
78
+ missing += target_count - sublist.size
79
+ end
80
+ end
81
+ missing
82
+ end
83
+ end
84
+ end
85
+ end
data/lib/inch/cli.rb CHANGED
@@ -17,11 +17,12 @@ module Inch
17
17
  end
18
18
 
19
19
  require_relative 'cli/arguments'
20
+ require_relative 'cli/weighted_list'
20
21
  require_relative 'cli/sparkline_helper'
21
22
  require_relative 'cli/trace_helper'
22
23
  require_relative 'cli/yardopts_helper'
24
+
23
25
  require_relative 'cli/command'
24
- require_relative 'cli/command_parser'
25
26
 
26
27
  console_rb = File.join(File.dirname(__FILE__), 'cli', 'command', 'console.rb')
27
28
  if File.exists?(console_rb)
@@ -85,6 +85,8 @@ module Inch
85
85
  def files
86
86
  object.files
87
87
  rescue YARD::CodeObjects::ProxyMethodError
88
+ # this error is raised by YARD
89
+ # see broken.rb in test fixtures
88
90
  []
89
91
  end
90
92
 
@@ -9,10 +9,10 @@ module Inch
9
9
  include NodocHelper
10
10
 
11
11
  # @return [YARD::CodeObjects::Base] the actual (YARD) code object
12
- attr_accessor :object
12
+ attr_reader :object
13
13
 
14
14
  # @return [Symbol]
15
- # when objects are assigned to ScoreRanges, this grade is set to
15
+ # when objects are assigned to GradeLists, this grade is set to
16
16
  # enable easier querying for objects of a certain grade
17
17
  attr_writer :grade
18
18
 
@@ -27,7 +27,7 @@ module Inch
27
27
 
28
28
  # @param object [YARD::CodeObjects::Base] the actual (YARD) code object
29
29
  def initialize(object)
30
- self.object = object
30
+ @object = object
31
31
  end
32
32
 
33
33
  def api_tag?
@@ -35,7 +35,14 @@ module Inch
35
35
  end
36
36
 
37
37
  def api_tag
38
- object.tag(:api) || (parent && parent.api_tag)
38
+ tag(:api) || (parent && parent.api_tag)
39
+ end
40
+
41
+ # To be overridden
42
+ # @see Proxy::NamespaceObject
43
+ # @return [CodeObject::Proxy::Base,nil] the child inside the current object or +nil+
44
+ def child(name)
45
+ nil
39
46
  end
40
47
 
41
48
  # To be overridden
@@ -65,8 +72,8 @@ module Inch
65
72
 
66
73
  # @return [Symbol]
67
74
  def grade
68
- @grade ||= Evaluation.new_score_ranges.detect { |range|
69
- range.range.include?(score)
75
+ @grade ||= Evaluation.new_grade_lists.detect { |range|
76
+ range.scores.include?(score)
70
77
  }.grade
71
78
  end
72
79
 
@@ -75,7 +82,7 @@ module Inch
75
82
  end
76
83
 
77
84
  def has_code_example?
78
- !object.tags(:example).empty? ||
85
+ !tags(:example).empty? ||
79
86
  docstring.contains_code_example?
80
87
  end
81
88
 
@@ -84,10 +91,10 @@ module Inch
84
91
  end
85
92
 
86
93
  def has_multiple_code_examples?
87
- if object.tags(:example).size > 1 || docstring.code_examples.size > 1
94
+ if tags(:example).size > 1 || docstring.code_examples.size > 1
88
95
  true
89
96
  else
90
- if tag = object.tag(:example)
97
+ if tag = tag(:example)
91
98
  multi_code_examples?(tag.text)
92
99
  elsif text = docstring.code_examples.first
93
100
  multi_code_examples?(text)
@@ -148,7 +155,11 @@ module Inch
148
155
  # @return [Boolean]
149
156
  # +true+ if the object or its parent is tagged as @private
150
157
  def private_tag?
151
- !object.tag(:private).nil? || (parent && parent.private_tag?)
158
+ !private_tag.nil?
159
+ end
160
+
161
+ def private_tag
162
+ tag(:private) || (parent && parent.private_tag)
152
163
  end
153
164
 
154
165
  def private_api_tag?
@@ -165,13 +176,13 @@ module Inch
165
176
 
166
177
  # @return [Boolean] +true+ if the object has no documentation at all
167
178
  def undocumented?
168
- docstring.empty? && object.tags.empty?
179
+ docstring.empty? && tags.empty?
169
180
  end
170
181
 
171
182
  # @return [Array]
172
183
  # YARD tags that are not already covered by other wrapper methods
173
184
  def unconsidered_tags
174
- @unconsidered_tags ||= object.tags.reject do |tag|
185
+ @unconsidered_tags ||= tags.reject do |tag|
175
186
  CONSIDERED_YARD_TAGS.include?(tag.tag_name)
176
187
  end
177
188
  end
@@ -180,11 +191,23 @@ module Inch
180
191
  "#<#{self.class.to_s}: #{path}>"
181
192
  end
182
193
 
183
- private
194
+ protected
184
195
 
185
196
  def multi_code_examples?(text)
186
197
  text.scan(/\b(#{Regexp.escape(name)})[^_0-9\!\?]/m).size > 1
187
198
  end
199
+
200
+ def tag(name)
201
+ tags(name).first
202
+ end
203
+
204
+ def tags(name = nil)
205
+ object.tags(name)
206
+ rescue YARD::CodeObjects::ProxyMethodError
207
+ # this error is raised by YARD
208
+ # see broken.rb in test fixtures
209
+ []
210
+ end
188
211
  end
189
212
  end
190
213
  end
@@ -16,6 +16,20 @@ module Inch
16
16
  name =~ /\!$/
17
17
  end
18
18
 
19
+ def getter?
20
+ attr_info = object.attr_info || {}
21
+ read_info = attr_info[:read]
22
+ if read_info
23
+ read_info.path == path
24
+ else
25
+ parent.child(:"#{name}=")
26
+ end
27
+ end
28
+
29
+ def has_doc?
30
+ super && !implicit_docstring?
31
+ end
32
+
19
33
  def has_parameters?
20
34
  !parameters.empty?
21
35
  end
@@ -68,6 +82,10 @@ module Inch
68
82
  (return_tag && !return_tag.text.empty?) || docstring.describes_return?
69
83
  end
70
84
 
85
+ def setter?
86
+ name =~ /\=$/ && parameters.size == 1
87
+ end
88
+
71
89
  def questioning_name?
72
90
  name =~ /\?$/
73
91
  end
@@ -109,6 +127,17 @@ module Inch
109
127
  lines.reverse
110
128
  end
111
129
 
130
+ def implicit_docstring?
131
+ if getter?
132
+ docstring == "Returns the value of attribute #{name}"
133
+ elsif setter?
134
+ basename = name.to_s.gsub(/(\=)$/, '')
135
+ docstring == "Sets the attribute #{basename}"
136
+ else
137
+ false
138
+ end
139
+ end
140
+
112
141
  def signature_parameter_names
113
142
  object.parameters.map(&:first)
114
143
  end
@@ -4,6 +4,12 @@ module Inch
4
4
  # a namespace object can have methods and other namespace objects
5
5
  # inside itself (e.g. classes and modules)
6
6
  class NamespaceObject < Base
7
+ def child(name)
8
+ if children
9
+ children.detect { |child| child.name == name }
10
+ end
11
+ end
12
+
7
13
  def children
8
14
  object.children.map do |o|
9
15
  Proxy.for(o)