cane 2.5.2 → 2.6.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.
- data/HISTORY.md +10 -0
- data/README.md +1 -0
- data/lib/cane/cli.rb +2 -0
- data/lib/cane/cli/options.rb +1 -0
- data/lib/cane/cli/parser.rb +3 -0
- data/lib/cane/default_checks.rb +1 -0
- data/lib/cane/doc_check.rb +47 -16
- data/lib/cane/encoding_aware_iterator.rb +2 -2
- data/lib/cane/file.rb +4 -0
- data/lib/cane/rake_task.rb +5 -1
- data/lib/cane/runner.rb +2 -1
- data/lib/cane/task_runner.rb +2 -1
- data/lib/cane/version.rb +1 -1
- data/lib/cane/violation_formatter.rb +17 -3
- data/spec/cane_spec.rb +9 -1
- data/spec/doc_check_spec.rb +55 -18
- data/spec/file_spec.rb +25 -0
- data/spec/rake_task_spec.rb +16 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/violation_formatter_spec.rb +18 -0
- metadata +4 -2
data/HISTORY.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Cane History
|
2
2
|
|
3
|
+
## 2.6.0 - 7 June 2013 (616bb8a5)
|
4
|
+
|
5
|
+
* Feature: classes with no methods do not require documentation.
|
6
|
+
* Feature: modules with methods require documentation.
|
7
|
+
* Feature: support all README extensions.
|
8
|
+
* Feature: --color option.
|
9
|
+
* Bugfix: fix false positive on class matching for doc check.
|
10
|
+
* Bugfix: better handling of invalid strings.
|
11
|
+
* Compat: fix Ruby 2.0 deprecations.
|
12
|
+
|
3
13
|
## 2.5.2 - 26 January 2013 (a0cf38ba)
|
4
14
|
|
5
15
|
* Feature: support operators beside `>=` in threshold check.
|
data/README.md
CHANGED
@@ -62,6 +62,7 @@ Customize behaviour with a wealth of options:
|
|
62
62
|
--max-violations VALUE Max allowed violations (default: 0)
|
63
63
|
--json Output as JSON
|
64
64
|
--parallel Use all processors. Slower on small projects, faster on large.
|
65
|
+
--color Colorize output
|
65
66
|
|
66
67
|
-v, --version Show version
|
67
68
|
-h, --help Show this message
|
data/lib/cane/cli.rb
CHANGED
data/lib/cane/cli/options.rb
CHANGED
data/lib/cane/cli/parser.rb
CHANGED
data/lib/cane/default_checks.rb
CHANGED
data/lib/cane/doc_check.rb
CHANGED
@@ -8,7 +8,15 @@ module Cane
|
|
8
8
|
class DocCheck < Struct.new(:opts)
|
9
9
|
|
10
10
|
DESCRIPTION =
|
11
|
-
|
11
|
+
"Class and Module definitions require explanatory comments on previous line"
|
12
|
+
|
13
|
+
ClassDefinition = Struct.new(:values) do
|
14
|
+
def line; values.fetch(:line); end
|
15
|
+
def label; values.fetch(:label); end
|
16
|
+
def missing_doc?; !values.fetch(:has_doc); end
|
17
|
+
def requires_doc?; values.fetch(:requires_doc, false); end
|
18
|
+
def requires_doc=(value); values[:requires_doc] = value; end
|
19
|
+
end
|
12
20
|
|
13
21
|
def self.key; :doc; end
|
14
22
|
def self.name; "documentation checking"; end
|
@@ -33,6 +41,10 @@ module Cane
|
|
33
41
|
MAGIC_COMMENT_REGEX =
|
34
42
|
%r"#(\s+-\*-)?\s+(en)?coding\s*[=:]\s*([[:alnum:]\-_]+)"
|
35
43
|
|
44
|
+
CLASS_REGEX = /^\s*(?:class|module)\s+([^\s;]+)/
|
45
|
+
|
46
|
+
METHOD_REGEX = /(?:^|\s)def\s+/
|
47
|
+
|
36
48
|
def violations
|
37
49
|
return [] if opts[:no_doc]
|
38
50
|
|
@@ -42,30 +54,46 @@ module Cane
|
|
42
54
|
end
|
43
55
|
|
44
56
|
def find_violations(file_name)
|
45
|
-
|
46
|
-
|
47
|
-
result = if class_definition?(line) && !comment?(last_line)
|
57
|
+
class_definitions_in(file_name).map do |class_definition|
|
58
|
+
if class_definition.requires_doc? && class_definition.missing_doc?
|
48
59
|
{
|
49
60
|
file: file_name,
|
50
|
-
line:
|
51
|
-
label:
|
61
|
+
line: class_definition.line,
|
62
|
+
label: class_definition.label,
|
52
63
|
description: DESCRIPTION
|
53
64
|
}
|
54
65
|
end
|
55
|
-
last_line = line
|
56
|
-
result
|
57
66
|
end.compact
|
58
67
|
end
|
59
68
|
|
69
|
+
def class_definitions_in(file_name)
|
70
|
+
class_definitions = []
|
71
|
+
last_line = ""
|
72
|
+
|
73
|
+
Cane::File.iterator(file_name).each_with_index do |line, number|
|
74
|
+
if class_definition? line
|
75
|
+
class_definitions << ClassDefinition.new({
|
76
|
+
line: (number + 1),
|
77
|
+
label: extract_class_name(line),
|
78
|
+
has_doc: comment?(last_line)
|
79
|
+
})
|
80
|
+
end
|
81
|
+
|
82
|
+
if method_definition?(line) && !class_definitions.empty?
|
83
|
+
class_definitions.last.requires_doc = true
|
84
|
+
end
|
85
|
+
|
86
|
+
last_line = line
|
87
|
+
end
|
88
|
+
|
89
|
+
class_definitions
|
90
|
+
end
|
91
|
+
|
60
92
|
def missing_file_violations
|
61
93
|
result = []
|
62
94
|
return result if opts[:no_readme]
|
63
95
|
|
64
|
-
|
65
|
-
extensions = ['', '.txt', '.md', '.mdown', '.rdoc']
|
66
|
-
combinations = filenames.product(extensions)
|
67
|
-
|
68
|
-
if combinations.none? {|n, x| Cane::File.exists?(n + x) }
|
96
|
+
if Cane::File.case_insensitive_glob("README*").none?
|
69
97
|
result << { description: 'Missing documentation',
|
70
98
|
label: 'No README found' }
|
71
99
|
end
|
@@ -76,8 +104,12 @@ module Cane
|
|
76
104
|
Dir[opts.fetch(:doc_glob)].reject { |file| excluded?(file) }
|
77
105
|
end
|
78
106
|
|
107
|
+
def method_definition?(line)
|
108
|
+
line =~ METHOD_REGEX
|
109
|
+
end
|
110
|
+
|
79
111
|
def class_definition?(line)
|
80
|
-
line =~
|
112
|
+
line =~ CLASS_REGEX && $1.index('<<') != 0
|
81
113
|
end
|
82
114
|
|
83
115
|
def comment?(line)
|
@@ -85,7 +117,7 @@ module Cane
|
|
85
117
|
end
|
86
118
|
|
87
119
|
def extract_class_name(line)
|
88
|
-
line.match(
|
120
|
+
line.match(CLASS_REGEX)[1]
|
89
121
|
end
|
90
122
|
|
91
123
|
def exclusions
|
@@ -102,5 +134,4 @@ module Cane
|
|
102
134
|
Cane.task_runner(opts)
|
103
135
|
end
|
104
136
|
end
|
105
|
-
|
106
137
|
end
|
@@ -9,13 +9,13 @@ module Cane
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def each(&block)
|
12
|
-
return
|
12
|
+
return self.to_enum unless block
|
13
13
|
|
14
14
|
lines.each do |line|
|
15
15
|
begin
|
16
16
|
line =~ /\s/
|
17
17
|
rescue ArgumentError
|
18
|
-
line.encode!('UTF-8', '
|
18
|
+
line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace)
|
19
19
|
end
|
20
20
|
|
21
21
|
block.call(line)
|
data/lib/cane/file.rb
CHANGED
data/lib/cane/rake_task.rb
CHANGED
@@ -54,7 +54,11 @@ module Cane
|
|
54
54
|
@gte = []
|
55
55
|
@options = Cane::CLI.default_options
|
56
56
|
|
57
|
-
|
57
|
+
if block_given?
|
58
|
+
yield self
|
59
|
+
else
|
60
|
+
self.canefile = './.cane'
|
61
|
+
end
|
58
62
|
|
59
63
|
unless ::Rake.application.last_comment
|
60
64
|
desc %(Check code quality metrics with cane)
|
data/lib/cane/runner.rb
CHANGED
@@ -3,6 +3,7 @@ require 'parallel'
|
|
3
3
|
require 'cane/violation_formatter'
|
4
4
|
require 'cane/json_formatter'
|
5
5
|
|
6
|
+
# Accepts a parsed configuration and passes those options to a new Runner
|
6
7
|
module Cane
|
7
8
|
def run(*args)
|
8
9
|
Runner.new(*args).run
|
@@ -19,7 +20,7 @@ module Cane
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def run
|
22
|
-
outputter.print formatter.new(violations)
|
23
|
+
outputter.print formatter.new(violations, opts)
|
23
24
|
|
24
25
|
violations.length <= opts.fetch(:max_violations)
|
25
26
|
end
|
data/lib/cane/task_runner.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# Provides a SimpleTaskRunner or Parallel task runner based on configuration
|
1
2
|
module Cane
|
2
3
|
def task_runner(opts)
|
3
4
|
if opts[:parallel]
|
@@ -8,7 +9,7 @@ module Cane
|
|
8
9
|
end
|
9
10
|
module_function :task_runner
|
10
11
|
|
11
|
-
# Mirrors the Parallel gem's interface but does not provide any
|
12
|
+
# Mirrors the Parallel gem's interface but does not provide any parallelism.
|
12
13
|
# This is faster for smaller tasks since it doesn't incur any overhead for
|
13
14
|
# creating new processes and communicating between them.
|
14
15
|
class SimpleTaskRunner
|
data/lib/cane/version.rb
CHANGED
@@ -6,24 +6,32 @@ module Cane
|
|
6
6
|
# Computes a string to be displayed as output from an array of violations
|
7
7
|
# computed by the checks.
|
8
8
|
class ViolationFormatter
|
9
|
-
attr_reader :violations
|
9
|
+
attr_reader :violations, :options
|
10
10
|
|
11
|
-
def initialize(violations)
|
11
|
+
def initialize(violations, options = {})
|
12
12
|
@violations = violations.map do |v|
|
13
13
|
v.merge(file_and_line: v[:line] ?
|
14
14
|
"%s:%i" % v.values_at(:file, :line) :
|
15
15
|
v[:file]
|
16
16
|
)
|
17
17
|
end
|
18
|
+
|
19
|
+
@options = options
|
18
20
|
end
|
19
21
|
|
20
22
|
def to_s
|
21
23
|
return "" if violations.empty?
|
22
24
|
|
23
|
-
violations.group_by {|x| x[:description] }.map do |d, vs|
|
25
|
+
string = violations.group_by {|x| x[:description] }.map do |d, vs|
|
24
26
|
format_group_header(d, vs) +
|
25
27
|
format_violations(vs)
|
26
28
|
end.join("\n") + "\n\n" + totals + "\n\n"
|
29
|
+
|
30
|
+
if violations.count > options.fetch(:max_violations, 0)
|
31
|
+
string = colorize(string)
|
32
|
+
end
|
33
|
+
|
34
|
+
string
|
27
35
|
end
|
28
36
|
|
29
37
|
protected
|
@@ -54,6 +62,12 @@ module Cane
|
|
54
62
|
}.join(' ').strip
|
55
63
|
end
|
56
64
|
|
65
|
+
def colorize(string)
|
66
|
+
return string unless options[:color]
|
67
|
+
|
68
|
+
"\e[31m#{string}\e[0m"
|
69
|
+
end
|
70
|
+
|
57
71
|
def totals
|
58
72
|
"Total Violations: #{violations.length}"
|
59
73
|
end
|
data/spec/cane_spec.rb
CHANGED
@@ -50,8 +50,10 @@ describe 'The cane application' do
|
|
50
50
|
--unhappy-file #{fn}
|
51
51
|
)
|
52
52
|
output.should include("Lines violated style requirements")
|
53
|
-
output.should include("Class definitions require explanatory comments")
|
54
53
|
output.should include("Methods exceeded maximum allowed ABC complexity")
|
54
|
+
output.should include(
|
55
|
+
"Class and Module definitions require explanatory comments"
|
56
|
+
)
|
55
57
|
exitstatus.should == 1
|
56
58
|
end
|
57
59
|
|
@@ -69,6 +71,12 @@ describe 'The cane application' do
|
|
69
71
|
Cane.task_runner(parallel: true).should == Parallel
|
70
72
|
end
|
71
73
|
|
74
|
+
it 'colorizes output' do
|
75
|
+
output, exitstatus = run("--color --abc-max 0")
|
76
|
+
|
77
|
+
output.should include("\e[31m")
|
78
|
+
end
|
79
|
+
|
72
80
|
after do
|
73
81
|
if Object.const_defined?(class_name)
|
74
82
|
Object.send(:remove_const, class_name)
|
data/spec/doc_check_spec.rb
CHANGED
@@ -7,38 +7,60 @@ describe Cane::DocCheck do
|
|
7
7
|
described_class.new(opts.merge(doc_glob: file_name))
|
8
8
|
end
|
9
9
|
|
10
|
-
it 'creates a DocViolation for each undocumented class' do
|
10
|
+
it 'creates a DocViolation for each undocumented class with a method' do
|
11
11
|
file_name = make_file <<-RUBY
|
12
12
|
# This class is documented
|
13
13
|
class Doc; end
|
14
|
-
class
|
15
|
-
class
|
14
|
+
class Empty; end # No doc is fine
|
15
|
+
class NoDoc; def with_method; end; end
|
16
16
|
classIgnore = nil
|
17
17
|
[:class]
|
18
18
|
# class Ignore
|
19
19
|
class Meta
|
20
20
|
class << self; end
|
21
|
+
end
|
22
|
+
module DontNeedDoc; end
|
23
|
+
# This module is documented
|
24
|
+
module HasDoc
|
25
|
+
def mixin; end
|
26
|
+
end
|
27
|
+
module AlsoNeedsDoc; def mixin; end; end
|
28
|
+
module NoDocIsFine
|
29
|
+
module ButThisNeedsDoc
|
30
|
+
def self.global
|
31
|
+
end
|
32
|
+
end
|
33
|
+
module AlsoNoDocIsFine; end
|
34
|
+
# We've got docs
|
35
|
+
module CauseWeNeedThem
|
36
|
+
def mixin
|
37
|
+
end
|
38
|
+
end
|
21
39
|
end
|
22
40
|
RUBY
|
23
41
|
|
24
42
|
violations = check(file_name).violations
|
25
|
-
violations.length.should ==
|
43
|
+
violations.length.should == 3
|
26
44
|
|
27
45
|
violations[0].values_at(:file, :line, :label).should == [
|
28
|
-
file_name,
|
46
|
+
file_name, 4, "NoDoc"
|
29
47
|
]
|
30
48
|
|
31
49
|
violations[1].values_at(:file, :line, :label).should == [
|
32
|
-
file_name,
|
50
|
+
file_name, 16, "AlsoNeedsDoc"
|
51
|
+
]
|
52
|
+
|
53
|
+
violations[2].values_at(:file, :line, :label).should == [
|
54
|
+
file_name, 18, "ButThisNeedsDoc"
|
33
55
|
]
|
34
56
|
end
|
35
57
|
|
36
58
|
it 'ignores magic encoding comments' do
|
37
59
|
file_name = make_file <<-RUBY
|
38
60
|
# coding = utf-8
|
39
|
-
class NoDoc; end
|
61
|
+
class NoDoc; def do_stuff; end; end
|
40
62
|
# -*- encoding : utf-8 -*-
|
41
|
-
class AlsoNoDoc; end
|
63
|
+
class AlsoNoDoc; def do_more_stuff; end; end
|
42
64
|
# Parse a Transfer-Encoding: Chunked response
|
43
65
|
class Doc; end
|
44
66
|
RUBY
|
@@ -57,16 +79,7 @@ class Doc; end
|
|
57
79
|
it 'creates a violation for missing README' do
|
58
80
|
file = fire_replaced_class_double("Cane::File")
|
59
81
|
stub_const("Cane::File", file)
|
60
|
-
file.should_receive(:
|
61
|
-
file.should_receive(:exists?).with("README.md").and_return(false)
|
62
|
-
file.should_receive(:exists?).with("README.mdown").and_return(false)
|
63
|
-
file.should_receive(:exists?).with("README.txt").and_return(false)
|
64
|
-
file.should_receive(:exists?).with("README.rdoc").and_return(false)
|
65
|
-
file.should_receive(:exists?).with("readme").and_return(false)
|
66
|
-
file.should_receive(:exists?).with("readme.md").and_return(false)
|
67
|
-
file.should_receive(:exists?).with("readme.mdown").and_return(false)
|
68
|
-
file.should_receive(:exists?).with("readme.txt").and_return(false)
|
69
|
-
file.should_receive(:exists?).with("readme.rdoc").and_return(false)
|
82
|
+
file.should_receive(:case_insensitive_glob).with("README*").and_return([])
|
70
83
|
|
71
84
|
violations = check("").violations
|
72
85
|
violations.length.should == 1
|
@@ -76,6 +89,18 @@ class Doc; end
|
|
76
89
|
]
|
77
90
|
end
|
78
91
|
|
92
|
+
it 'does not create a violation when readme exists' do
|
93
|
+
file = fire_replaced_class_double("Cane::File")
|
94
|
+
stub_const("Cane::File", file)
|
95
|
+
file
|
96
|
+
.should_receive(:case_insensitive_glob)
|
97
|
+
.with("README*")
|
98
|
+
.and_return(%w(readme.md))
|
99
|
+
|
100
|
+
violations = check("").violations
|
101
|
+
violations.length.should == 0
|
102
|
+
end
|
103
|
+
|
79
104
|
it 'skips declared exclusions' do
|
80
105
|
file_name = make_file <<-FILE.gsub /^\s{6}/, ''
|
81
106
|
class NeedsDocumentation
|
@@ -101,4 +126,16 @@ class Doc; end
|
|
101
126
|
|
102
127
|
violations.length.should == 0
|
103
128
|
end
|
129
|
+
|
130
|
+
it 'skips class inside an array' do
|
131
|
+
file_name = make_file <<-RUBY
|
132
|
+
%w(
|
133
|
+
class
|
134
|
+
method
|
135
|
+
)
|
136
|
+
RUBY
|
137
|
+
|
138
|
+
violations = check(file_name).violations
|
139
|
+
violations.length.should == 0
|
140
|
+
end
|
104
141
|
end
|
data/spec/file_spec.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
require 'cane/file'
|
5
|
+
|
6
|
+
describe Cane::File do
|
7
|
+
describe '.case_insensitive_glob' do
|
8
|
+
it 'matches all kinds of readmes' do
|
9
|
+
expected = %w(
|
10
|
+
README
|
11
|
+
readme.md
|
12
|
+
ReaDME.TEXTILE
|
13
|
+
)
|
14
|
+
|
15
|
+
Dir.mktmpdir do |dir|
|
16
|
+
Dir.chdir(dir) do
|
17
|
+
expected.each do |x|
|
18
|
+
FileUtils.touch(x)
|
19
|
+
end
|
20
|
+
Cane::File.case_insensitive_glob("README*").should =~ expected
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/rake_task_spec.rb
CHANGED
@@ -47,6 +47,22 @@ describe Cane::RakeTask do
|
|
47
47
|
out.should include("Quality threshold crossed")
|
48
48
|
end
|
49
49
|
|
50
|
+
it 'defaults to using a canefile without a block' do
|
51
|
+
in_tmp_dir do
|
52
|
+
conf = "--gte 90,99"
|
53
|
+
conf_file = File.open('.cane', 'w') {|f| f.write conf }
|
54
|
+
|
55
|
+
task = Cane::RakeTask.new(:canefile_quality)
|
56
|
+
|
57
|
+
task.should_receive(:abort)
|
58
|
+
out = capture_stdout do
|
59
|
+
Rake::Task['canefile_quality'].invoke
|
60
|
+
end
|
61
|
+
|
62
|
+
out.should include("Quality threshold crossed")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
50
66
|
after do
|
51
67
|
Rake::Task.clear
|
52
68
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -18,4 +18,22 @@ describe Cane::ViolationFormatter do
|
|
18
18
|
result = described_class.new(violations).to_s
|
19
19
|
result.should include("Total Violations: 2")
|
20
20
|
end
|
21
|
+
|
22
|
+
it 'does not colorize output by default' do
|
23
|
+
result = described_class.new([violation("FAIL")]).to_s
|
24
|
+
result.should_not include("\e[31m")
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'colorizes output when passed color: true' do
|
28
|
+
result = described_class.new([violation("FAIL")], color: true).to_s
|
29
|
+
result.should include("\e[31m")
|
30
|
+
result.should include("\e[0m")
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does not colorize output if max_violations is not crossed' do
|
34
|
+
options = { color: true, max_violations: 1 }
|
35
|
+
result = described_class.new([violation("FAIL")], options).to_s
|
36
|
+
|
37
|
+
result.should_not include("\e[31m")
|
38
|
+
end
|
21
39
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: parallel
|
@@ -104,6 +104,7 @@ files:
|
|
104
104
|
- spec/cli_spec.rb
|
105
105
|
- spec/doc_check_spec.rb
|
106
106
|
- spec/encoding_aware_iterator_spec.rb
|
107
|
+
- spec/file_spec.rb
|
107
108
|
- spec/json_formatter_spec.rb
|
108
109
|
- spec/parser_spec.rb
|
109
110
|
- spec/rake_task_spec.rb
|
@@ -165,6 +166,7 @@ test_files:
|
|
165
166
|
- spec/cli_spec.rb
|
166
167
|
- spec/doc_check_spec.rb
|
167
168
|
- spec/encoding_aware_iterator_spec.rb
|
169
|
+
- spec/file_spec.rb
|
168
170
|
- spec/json_formatter_spec.rb
|
169
171
|
- spec/parser_spec.rb
|
170
172
|
- spec/rake_task_spec.rb
|