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.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +335 -3
- data/Rakefile +8 -0
- data/TODOS.md +12 -0
- data/bin/inch +17 -0
- data/inch.gemspec +7 -2
- data/lib/inch.rb +6 -1
- data/lib/inch/cli.rb +24 -0
- data/lib/inch/cli/arguments.rb +45 -0
- data/lib/inch/cli/command.rb +29 -0
- data/lib/inch/cli/command/base.rb +62 -0
- data/lib/inch/cli/command/base_list.rb +75 -0
- data/lib/inch/cli/command/base_object.rb +40 -0
- data/lib/inch/cli/command/console.rb +22 -0
- data/lib/inch/cli/command/inspect.rb +20 -0
- data/lib/inch/cli/command/list.rb +25 -0
- data/lib/inch/cli/command/options/base.rb +137 -0
- data/lib/inch/cli/command/options/base_list.rb +84 -0
- data/lib/inch/cli/command/options/base_object.rb +47 -0
- data/lib/inch/cli/command/options/console.rb +26 -0
- data/lib/inch/cli/command/options/inspect.rb +25 -0
- data/lib/inch/cli/command/options/list.rb +30 -0
- data/lib/inch/cli/command/options/show.rb +27 -0
- data/lib/inch/cli/command/options/stats.rb +20 -0
- data/lib/inch/cli/command/options/suggest.rb +61 -0
- data/lib/inch/cli/command/output/base.rb +32 -0
- data/lib/inch/cli/command/output/console.rb +45 -0
- data/lib/inch/cli/command/output/inspect.rb +129 -0
- data/lib/inch/cli/command/output/list.rb +87 -0
- data/lib/inch/cli/command/output/show.rb +79 -0
- data/lib/inch/cli/command/output/stats.rb +111 -0
- data/lib/inch/cli/command/output/suggest.rb +104 -0
- data/lib/inch/cli/command/show.rb +20 -0
- data/lib/inch/cli/command/stats.rb +20 -0
- data/lib/inch/cli/command/suggest.rb +104 -0
- data/lib/inch/cli/command_parser.rb +82 -0
- data/lib/inch/cli/sparkline_helper.rb +31 -0
- data/lib/inch/cli/trace_helper.rb +42 -0
- data/lib/inch/cli/yardopts_helper.rb +49 -0
- data/lib/inch/code_object.rb +8 -0
- data/lib/inch/code_object/docstring.rb +97 -0
- data/lib/inch/code_object/nodoc_helper.rb +84 -0
- data/lib/inch/code_object/proxy.rb +37 -0
- data/lib/inch/code_object/proxy/base.rb +194 -0
- data/lib/inch/code_object/proxy/class_object.rb +9 -0
- data/lib/inch/code_object/proxy/constant_object.rb +8 -0
- data/lib/inch/code_object/proxy/method_object.rb +118 -0
- data/lib/inch/code_object/proxy/method_parameter_object.rb +81 -0
- data/lib/inch/code_object/proxy/module_object.rb +8 -0
- data/lib/inch/code_object/proxy/namespace_object.rb +38 -0
- data/lib/inch/core_ext.rb +2 -0
- data/lib/inch/core_ext/string.rb +3 -0
- data/lib/inch/core_ext/yard.rb +4 -0
- data/lib/inch/evaluation.rb +35 -0
- data/lib/inch/evaluation/base.rb +60 -0
- data/lib/inch/evaluation/class_object.rb +6 -0
- data/lib/inch/evaluation/constant_object.rb +34 -0
- data/lib/inch/evaluation/file.rb +66 -0
- data/lib/inch/evaluation/method_object.rb +127 -0
- data/lib/inch/evaluation/module_object.rb +6 -0
- data/lib/inch/evaluation/namespace_object.rb +94 -0
- data/lib/inch/evaluation/role/base.rb +49 -0
- data/lib/inch/evaluation/role/constant.rb +43 -0
- data/lib/inch/evaluation/role/method.rb +60 -0
- data/lib/inch/evaluation/role/method_parameter.rb +46 -0
- data/lib/inch/evaluation/role/missing.rb +20 -0
- data/lib/inch/evaluation/role/namespace.rb +58 -0
- data/lib/inch/evaluation/role/object.rb +64 -0
- data/lib/inch/evaluation/score_range.rb +26 -0
- data/lib/inch/rake.rb +1 -0
- data/lib/inch/rake/suggest.rb +26 -0
- data/lib/inch/source_parser.rb +36 -0
- data/lib/inch/version.rb +1 -1
- data/test/fixtures/code_examples/lib/foo.rb +87 -0
- data/test/fixtures/readme/lib/foo.rb +17 -0
- data/test/fixtures/simple/README +25 -0
- data/test/fixtures/simple/lib/broken.rb +10 -0
- data/test/fixtures/simple/lib/foo.rb +214 -0
- data/test/fixtures/simple/lib/role_methods.rb +78 -0
- data/test/fixtures/simple/lib/role_namespaces.rb +68 -0
- data/test/fixtures/visibility/lib/foo.rb +18 -0
- data/test/fixtures/yardopts/.yardopts +1 -0
- data/test/fixtures/yardopts/foo/bar.rb +6 -0
- data/test/inch/cli/arguments_test.rb +70 -0
- data/test/inch/cli/command/console_test.rb +59 -0
- data/test/inch/cli/command/inspect_test.rb +59 -0
- data/test/inch/cli/command/list_test.rb +61 -0
- data/test/inch/cli/command/show_test.rb +59 -0
- data/test/inch/cli/command/stats_test.rb +57 -0
- data/test/inch/cli/command/suggest_test.rb +57 -0
- data/test/inch/cli/command_parser_test.rb +33 -0
- data/test/inch/cli/yardopts_helper_test.rb +84 -0
- data/test/inch/code_object/docstring_test.rb +204 -0
- data/test/inch/code_object/nodoc_helper_test.rb +38 -0
- data/test/inch/code_object/proxy_test.rb +188 -0
- data/test/inch/source_parser_test.rb +23 -0
- data/test/integration/stats_options_test.rb +34 -0
- data/test/integration/visibility_options_test.rb +79 -0
- data/test/test_helper.rb +21 -0
- metadata +184 -7
@@ -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,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,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,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
|