inch 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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