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