inch 0.0.1 → 0.1.0

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/README.md +335 -3
  4. data/Rakefile +8 -0
  5. data/TODOS.md +12 -0
  6. data/bin/inch +17 -0
  7. data/inch.gemspec +7 -2
  8. data/lib/inch.rb +6 -1
  9. data/lib/inch/cli.rb +24 -0
  10. data/lib/inch/cli/arguments.rb +45 -0
  11. data/lib/inch/cli/command.rb +29 -0
  12. data/lib/inch/cli/command/base.rb +62 -0
  13. data/lib/inch/cli/command/base_list.rb +75 -0
  14. data/lib/inch/cli/command/base_object.rb +40 -0
  15. data/lib/inch/cli/command/console.rb +22 -0
  16. data/lib/inch/cli/command/inspect.rb +20 -0
  17. data/lib/inch/cli/command/list.rb +25 -0
  18. data/lib/inch/cli/command/options/base.rb +137 -0
  19. data/lib/inch/cli/command/options/base_list.rb +84 -0
  20. data/lib/inch/cli/command/options/base_object.rb +47 -0
  21. data/lib/inch/cli/command/options/console.rb +26 -0
  22. data/lib/inch/cli/command/options/inspect.rb +25 -0
  23. data/lib/inch/cli/command/options/list.rb +30 -0
  24. data/lib/inch/cli/command/options/show.rb +27 -0
  25. data/lib/inch/cli/command/options/stats.rb +20 -0
  26. data/lib/inch/cli/command/options/suggest.rb +61 -0
  27. data/lib/inch/cli/command/output/base.rb +32 -0
  28. data/lib/inch/cli/command/output/console.rb +45 -0
  29. data/lib/inch/cli/command/output/inspect.rb +129 -0
  30. data/lib/inch/cli/command/output/list.rb +87 -0
  31. data/lib/inch/cli/command/output/show.rb +79 -0
  32. data/lib/inch/cli/command/output/stats.rb +111 -0
  33. data/lib/inch/cli/command/output/suggest.rb +104 -0
  34. data/lib/inch/cli/command/show.rb +20 -0
  35. data/lib/inch/cli/command/stats.rb +20 -0
  36. data/lib/inch/cli/command/suggest.rb +104 -0
  37. data/lib/inch/cli/command_parser.rb +82 -0
  38. data/lib/inch/cli/sparkline_helper.rb +31 -0
  39. data/lib/inch/cli/trace_helper.rb +42 -0
  40. data/lib/inch/cli/yardopts_helper.rb +49 -0
  41. data/lib/inch/code_object.rb +8 -0
  42. data/lib/inch/code_object/docstring.rb +97 -0
  43. data/lib/inch/code_object/nodoc_helper.rb +84 -0
  44. data/lib/inch/code_object/proxy.rb +37 -0
  45. data/lib/inch/code_object/proxy/base.rb +194 -0
  46. data/lib/inch/code_object/proxy/class_object.rb +9 -0
  47. data/lib/inch/code_object/proxy/constant_object.rb +8 -0
  48. data/lib/inch/code_object/proxy/method_object.rb +118 -0
  49. data/lib/inch/code_object/proxy/method_parameter_object.rb +81 -0
  50. data/lib/inch/code_object/proxy/module_object.rb +8 -0
  51. data/lib/inch/code_object/proxy/namespace_object.rb +38 -0
  52. data/lib/inch/core_ext.rb +2 -0
  53. data/lib/inch/core_ext/string.rb +3 -0
  54. data/lib/inch/core_ext/yard.rb +4 -0
  55. data/lib/inch/evaluation.rb +35 -0
  56. data/lib/inch/evaluation/base.rb +60 -0
  57. data/lib/inch/evaluation/class_object.rb +6 -0
  58. data/lib/inch/evaluation/constant_object.rb +34 -0
  59. data/lib/inch/evaluation/file.rb +66 -0
  60. data/lib/inch/evaluation/method_object.rb +127 -0
  61. data/lib/inch/evaluation/module_object.rb +6 -0
  62. data/lib/inch/evaluation/namespace_object.rb +94 -0
  63. data/lib/inch/evaluation/role/base.rb +49 -0
  64. data/lib/inch/evaluation/role/constant.rb +43 -0
  65. data/lib/inch/evaluation/role/method.rb +60 -0
  66. data/lib/inch/evaluation/role/method_parameter.rb +46 -0
  67. data/lib/inch/evaluation/role/missing.rb +20 -0
  68. data/lib/inch/evaluation/role/namespace.rb +58 -0
  69. data/lib/inch/evaluation/role/object.rb +64 -0
  70. data/lib/inch/evaluation/score_range.rb +26 -0
  71. data/lib/inch/rake.rb +1 -0
  72. data/lib/inch/rake/suggest.rb +26 -0
  73. data/lib/inch/source_parser.rb +36 -0
  74. data/lib/inch/version.rb +1 -1
  75. data/test/fixtures/code_examples/lib/foo.rb +87 -0
  76. data/test/fixtures/readme/lib/foo.rb +17 -0
  77. data/test/fixtures/simple/README +25 -0
  78. data/test/fixtures/simple/lib/broken.rb +10 -0
  79. data/test/fixtures/simple/lib/foo.rb +214 -0
  80. data/test/fixtures/simple/lib/role_methods.rb +78 -0
  81. data/test/fixtures/simple/lib/role_namespaces.rb +68 -0
  82. data/test/fixtures/visibility/lib/foo.rb +18 -0
  83. data/test/fixtures/yardopts/.yardopts +1 -0
  84. data/test/fixtures/yardopts/foo/bar.rb +6 -0
  85. data/test/inch/cli/arguments_test.rb +70 -0
  86. data/test/inch/cli/command/console_test.rb +59 -0
  87. data/test/inch/cli/command/inspect_test.rb +59 -0
  88. data/test/inch/cli/command/list_test.rb +61 -0
  89. data/test/inch/cli/command/show_test.rb +59 -0
  90. data/test/inch/cli/command/stats_test.rb +57 -0
  91. data/test/inch/cli/command/suggest_test.rb +57 -0
  92. data/test/inch/cli/command_parser_test.rb +33 -0
  93. data/test/inch/cli/yardopts_helper_test.rb +84 -0
  94. data/test/inch/code_object/docstring_test.rb +204 -0
  95. data/test/inch/code_object/nodoc_helper_test.rb +38 -0
  96. data/test/inch/code_object/proxy_test.rb +188 -0
  97. data/test/inch/source_parser_test.rb +23 -0
  98. data/test/integration/stats_options_test.rb +34 -0
  99. data/test/integration/visibility_options_test.rb +79 -0
  100. data/test/test_helper.rb +21 -0
  101. metadata +184 -7
@@ -0,0 +1,9 @@
1
+ module Inch
2
+ module CodeObject
3
+ module Proxy
4
+ class ClassObject < NamespaceObject
5
+ def_delegators :object, :superclass
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module Inch
2
+ module CodeObject
3
+ module Proxy
4
+ class ConstantObject < Base
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,118 @@
1
+ module Inch
2
+ module CodeObject
3
+ module Proxy
4
+ class MethodObject < Base
5
+ def comment_and_abbrev_source
6
+ comments.join('') + abbrev_source
7
+ end
8
+
9
+ def bang_name?
10
+ name =~ /\!$/
11
+ end
12
+
13
+ def has_parameters?
14
+ !parameters.empty?
15
+ end
16
+
17
+ MANY_PARAMETERS_THRESHOLD = 3
18
+ def has_many_parameters?
19
+ parameters.size > MANY_PARAMETERS_THRESHOLD
20
+ end
21
+
22
+ MANY_LINES_THRESHOLD = 20
23
+ def has_many_lines?
24
+ # for now, this includes the 'def' line and comments
25
+ if source = object.source
26
+ size = source.lines.count
27
+ size > MANY_LINES_THRESHOLD
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ def method?
34
+ true
35
+ end
36
+
37
+ def parameters
38
+ @parameters ||= all_parameter_names.map do |name|
39
+ in_signature = signature_parameter_names.include?(name)
40
+ tag = parameter_tag(name)
41
+ MethodParameterObject.new(self, name, tag, in_signature)
42
+ end
43
+ end
44
+
45
+ def parameter(name)
46
+ parameters.detect { |p| p.name == name.to_s }
47
+ end
48
+
49
+ def overridden?
50
+ !!object.overridden_method
51
+ end
52
+
53
+ def overridden_method
54
+ @overridden_method ||= Proxy.for(object.overridden_method)
55
+ end
56
+
57
+ def return_mentioned?
58
+ !!return_tag || docstring.mentions_return?
59
+ end
60
+
61
+ private
62
+
63
+ def all_parameter_names
64
+ names = signature_parameter_names
65
+ names.concat parameter_tags.map(&:name)
66
+ names.compact.uniq
67
+ end
68
+
69
+ def abbrev_source
70
+ lines = object.source.to_s.lines
71
+ if lines.size >= 5
72
+ indent = lines[1].scan(/^(\s+)/).flatten.join('')
73
+ lines = lines[0..1] +
74
+ ["#{indent}# ... snip ...\n"] +
75
+ lines[-2..-1]
76
+ end
77
+ lines.join('')
78
+ end
79
+
80
+ def comments
81
+ @comments ||= files.map do |(filename, line_no)|
82
+ get_lines_up_while(filename, line_no - 1) do |line|
83
+ line =~ /^\s*#/
84
+ end.flatten.join('')
85
+ end
86
+ end
87
+
88
+ def get_lines_up_while(filename, line_no, &block)
89
+ lines = []
90
+ line = get_line_no(filename, line_no)
91
+ if yield(line) && line_no > 0
92
+ lines << line.gsub(/^(\s+)/, '')
93
+ lines << get_lines_up_while(filename, line_no - 1, &block)
94
+ end
95
+ lines.reverse
96
+ end
97
+
98
+ def signature_parameter_names
99
+ object.parameters.map(&:first)
100
+ end
101
+
102
+ def parameter_tag(param_name)
103
+ parameter_tags.detect do |tag|
104
+ tag.name == param_name
105
+ end
106
+ end
107
+
108
+ def parameter_tags
109
+ object.tags(:param)
110
+ end
111
+
112
+ def return_tag
113
+ object.tags(:return).first
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,81 @@
1
+ module Inch
2
+ module CodeObject
3
+ module Proxy
4
+ class MethodParameterObject
5
+ attr_reader :name
6
+
7
+ # @param method [Inch::CodeObject::Proxy::MethodObject] the method the parameter belongs_to
8
+ # @param name [String] the name of the parameter
9
+ # @param tag [YARD::Tags::Tag] the Tag object for the parameter
10
+ # @param in_signature [Boolean] +true+ if the method's signature contains the parameter
11
+ def initialize(method, name, tag, in_signature)
12
+ @method = method
13
+ @name = name
14
+ @tag = tag
15
+ @in_signature = in_signature
16
+ end
17
+
18
+ BAD_NAME_EXCEPTIONS = %w(id)
19
+ BAD_NAME_THRESHOLD = 3
20
+ def bad_name?
21
+ return false if BAD_NAME_EXCEPTIONS.include?(name)
22
+ name.size < BAD_NAME_THRESHOLD || name =~ /[0-9]$/
23
+ end
24
+
25
+ # @return [Boolean] +true+ if the parameter is a block
26
+ def block?
27
+ name =~ /^\&/
28
+ end
29
+
30
+ # @return [Boolean] +true+ if an additional description given?
31
+ def described?
32
+ described_by_tag? || described_by_docstring?
33
+ end
34
+
35
+ # @return [Boolean] +true+ if the parameter is mentioned in the docs
36
+ def mentioned?
37
+ !!@tag || mentioned_by_docstring?
38
+ end
39
+
40
+ # @return [Boolean] +true+ if the parameter is a splat argument
41
+ def splat?
42
+ name =~ /^\*/
43
+ end
44
+
45
+ # @return [Boolean] +true+ if the type of the parameter is defined
46
+ def typed?
47
+ @tag && @tag.types && !@tag.types.empty?
48
+ end
49
+
50
+ # @return [Boolean] +true+ if the parameter is mentioned in the docs, but not present in the method's signature
51
+ def wrongly_mentioned?
52
+ mentioned? && !@in_signature
53
+ end
54
+
55
+ private
56
+
57
+ def described_by_tag?
58
+ @tag && !@tag.text.empty?
59
+ end
60
+
61
+ def described_by_docstring?
62
+ if @method.docstring.describes_parameter?(name)
63
+ true
64
+ else
65
+ unsplatted = name.gsub(/^[\&\*]/, '')
66
+ @method.docstring.describes_parameter?(unsplatted)
67
+ end
68
+ end
69
+
70
+ def mentioned_by_docstring?
71
+ if @method.docstring.mentions_parameter?(name)
72
+ true
73
+ else
74
+ unsplatted = name.gsub(/^[\&\*]/, '')
75
+ @method.docstring.mentions_parameter?(unsplatted)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,8 @@
1
+ module Inch
2
+ module CodeObject
3
+ module Proxy
4
+ class ModuleObject < NamespaceObject
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,38 @@
1
+ module Inch
2
+ module CodeObject
3
+ module Proxy
4
+ # a namespace object can have methods and other namespace objects
5
+ # inside itself (e.g. classes and modules)
6
+ class NamespaceObject < Base
7
+ def children
8
+ object.children.map do |o|
9
+ Proxy.for(o)
10
+ end
11
+ end
12
+
13
+ MANY_ATTRIBUTES_THRESHOLD = 5
14
+ def has_many_attributes?
15
+ n = object.class_attributes.size + object.instance_attributes.size
16
+ n > MANY_ATTRIBUTES_THRESHOLD
17
+ end
18
+
19
+ MANY_CHILDREN_THRESHOLD = 20
20
+ def has_many_children?
21
+ children.size > MANY_CHILDREN_THRESHOLD
22
+ end
23
+
24
+ def namespace?
25
+ true
26
+ end
27
+
28
+ def no_methods?
29
+ !children.any?(&:method?)
30
+ end
31
+
32
+ def pure_namespace?
33
+ children.all?(&:namespace?)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'core_ext/yard'
2
+ require_relative 'core_ext/string'
@@ -0,0 +1,3 @@
1
+ require 'term/ansicolor'
2
+
3
+ String.send(:include, Term::ANSIColor)
@@ -0,0 +1,4 @@
1
+ require 'logger'
2
+ require 'yard'
3
+
4
+ log.level = ::Logger::UNKNOWN # basically disable YARD's logging
@@ -0,0 +1,35 @@
1
+ module Inch
2
+ module Evaluation
3
+ def self.for(code_object)
4
+ class_for(code_object).new(code_object)
5
+ end
6
+
7
+ private
8
+
9
+ def self.class_for(code_object)
10
+ class_name = code_object.class.to_s.split('::').last
11
+ eval(class_name)
12
+ rescue
13
+ Base
14
+ end
15
+ end
16
+ end
17
+
18
+ require_relative 'evaluation/file'
19
+
20
+ require_relative 'evaluation/score_range'
21
+
22
+ require_relative 'evaluation/role/base'
23
+ require_relative 'evaluation/role/missing'
24
+ require_relative 'evaluation/role/object'
25
+ require_relative 'evaluation/role/method'
26
+ require_relative 'evaluation/role/method_parameter'
27
+ require_relative 'evaluation/role/namespace'
28
+ require_relative 'evaluation/role/constant'
29
+
30
+ require_relative 'evaluation/base'
31
+ require_relative 'evaluation/namespace_object'
32
+ require_relative 'evaluation/class_object'
33
+ require_relative 'evaluation/constant_object'
34
+ require_relative 'evaluation/method_object'
35
+ require_relative 'evaluation/module_object'
@@ -0,0 +1,60 @@
1
+ module Inch
2
+ module Evaluation
3
+ class Base
4
+ extend Forwardable
5
+
6
+ MIN_SCORE = 0
7
+ MAX_SCORE = 100
8
+
9
+ TAGGED_SCORE = 20 # assigned per unconsidered tag
10
+
11
+
12
+ attr_accessor :object
13
+ attr_reader :min_score, :max_score
14
+
15
+ def initialize(object)
16
+ self.object = object
17
+ @roles = []
18
+ evaluate
19
+ end
20
+
21
+ def evaluate
22
+ end
23
+
24
+ def max_score
25
+ arr = @roles.map(&:max_score).compact
26
+ [MAX_SCORE].concat(arr).min
27
+ end
28
+
29
+ def min_score
30
+ arr = @roles.map(&:min_score).compact
31
+ [MIN_SCORE].concat(arr).max
32
+ end
33
+
34
+ def score
35
+ value = @roles.inject(0) { |sum,r| sum + r.score.to_f }
36
+ if value < min_score
37
+ min_score
38
+ elsif value > max_score
39
+ max_score
40
+ else
41
+ value
42
+ end
43
+ end
44
+
45
+ def priority
46
+ @roles.inject(0) { |sum,r| sum + r.priority.to_i }
47
+ end
48
+
49
+ def roles
50
+ @roles
51
+ end
52
+
53
+ protected
54
+
55
+ def add_role(role)
56
+ @roles << role
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,6 @@
1
+ module Inch
2
+ module Evaluation
3
+ class ClassObject < NamespaceObject
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,34 @@
1
+ module Inch
2
+ module Evaluation
3
+ class ConstantObject < Base
4
+ DOC_SCORE = MAX_SCORE
5
+
6
+ def evaluate
7
+ if object.has_doc?
8
+ add_role Role::Constant::WithDoc.new(object, DOC_SCORE)
9
+ else
10
+ add_role Role::Constant::WithoutDoc.new(object, DOC_SCORE)
11
+ end
12
+ if object.nodoc?
13
+ add_role Role::Constant::TaggedAsNodoc.new(object)
14
+ end
15
+ if object.has_unconsidered_tags?
16
+ count = object.unconsidered_tags.size
17
+ add_role Role::Object::Tagged.new(object, TAGGED_SCORE * count)
18
+ end
19
+ if object.in_root?
20
+ add_role Role::Constant::InRoot.new(object)
21
+ end
22
+ if object.public?
23
+ add_role Role::Constant::Public.new(object)
24
+ end
25
+ if object.protected?
26
+ add_role Role::Constant::Protected.new(object)
27
+ end
28
+ if object.private?
29
+ add_role Role::Constant::Private.new(object)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,66 @@
1
+ module Inch
2
+ module Evaluation
3
+ class File
4
+ attr_accessor :filename, :objects
5
+
6
+ def initialize(filename, objects)
7
+ self.filename = filename
8
+ self.objects = objects.select do |o|
9
+ o.filename == filename
10
+ end
11
+ end
12
+
13
+ # @note added to be compatible with code objects
14
+ def path
15
+ filename
16
+ end
17
+
18
+ #
19
+ # grade, priority, and score are not meant to be displayed in the CLI
20
+ # they are just for internal evaluation purposes
21
+ #
22
+
23
+ def grade
24
+ median(grades.sort)
25
+ end
26
+
27
+ def priority
28
+ median(priorities.sort)
29
+ end
30
+
31
+ def score
32
+ objects.select(&:undocumented?).size
33
+ end
34
+
35
+ private
36
+
37
+ def grades
38
+ objects.map(&:grade)
39
+ end
40
+
41
+ def priorities
42
+ objects.map(&:priority)
43
+ end
44
+
45
+ def scores
46
+ objects.map(&:score)
47
+ end
48
+
49
+ def median(sorted_list)
50
+ index = (sorted_list.size / 2).round
51
+ sorted_list[index]
52
+ end
53
+
54
+ class << self
55
+ def for(filename, objects)
56
+ @cache ||= {}
57
+ if file = @cache[filename]
58
+ file
59
+ else
60
+ @cache[filename] = new(filename, objects)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end