cane 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Cane History
2
2
 
3
+ ## 1.4.0 - 2 July 2012 (1afc999d)
4
+
5
+ * Allow files and methods to be whitelisted (#16)
6
+ * Show total number of violations in output (#14)
7
+
3
8
  ## 1.3.0 - 20 April 2012 (c166dfa0)
4
9
 
5
10
  * Remove dependency on tailor. Fewer styles checks are performed, but the three
data/README.md CHANGED
@@ -48,6 +48,7 @@ Customize behaviour with a wealth of options:
48
48
  --gte FILE,THRESHOLD If FILE contains a number, verify it is >= to THRESHOLD.
49
49
 
50
50
  --max-violations VALUE Max allowed violations (default: 0)
51
+ --exclusions-file FILE YAML file containing a list of exclusions
51
52
 
52
53
  --version Show version
53
54
  -h, --help Show this message
@@ -91,6 +92,8 @@ on to an existing application that may already have many violations. By setting
91
92
  the maximum to the current number, no immediate changes will be required to
92
93
  your existing code base, but you will be protected from things getting worse.
93
94
 
95
+ You can also consider defining exclusions for each violation (see below).
96
+
94
97
  ## Integrating with SimpleCov
95
98
 
96
99
  Any value in a file can be used as a threshold:
@@ -115,6 +118,24 @@ You can use a `SimpleCov` formatter to create the required file:
115
118
 
116
119
  SimpleCov.formatter = SimpleCov::Formatter::QualityFormatter
117
120
 
121
+ ## Defining Exclusions
122
+
123
+ Occasionally, you may want to permanently ignore specific cane violations.
124
+ Create a YAML file like so:
125
+
126
+ abc:
127
+ - Some::Fully::Qualified::Class.some_class_method
128
+ - Some::Fully::Qualified::Class#some_instance_method
129
+ style:
130
+ relative/path/to/some/file.rb
131
+ relative/path/to/some/other/file.rb
132
+
133
+ Tell cane about this file using the `--exclusions-file` option:
134
+
135
+ > cane --exclusions-file path/to/exclusions.yml
136
+
137
+ Currently, only the abc and style checks support exclusions.
138
+
118
139
  ## Compatibility
119
140
 
120
141
  Requires MRI 1.9, since it depends on the `ripper` library to calculate
@@ -2,6 +2,7 @@ require 'ripper'
2
2
 
3
3
  require 'cane/abc_max_violation'
4
4
  require 'cane/syntax_violation'
5
+ require 'set'
5
6
 
6
7
  module Cane
7
8
 
@@ -23,7 +24,7 @@ module Cane
23
24
  when nil
24
25
  InvalidAst.new(file_name)
25
26
  else
26
- RubyAst.new(file_name, max_allowed_complexity, ast)
27
+ RubyAst.new(file_name, max_allowed_complexity, ast, exclusions)
27
28
  end.violations
28
29
  end
29
30
 
@@ -35,7 +36,8 @@ module Cane
35
36
  end
36
37
 
37
38
  # Wrapper object around sexps returned from ripper.
38
- class RubyAst < Struct.new(:file_name, :max_allowed_complexity, :sexps)
39
+ class RubyAst < Struct.new(:file_name, :max_allowed_complexity,
40
+ :sexps, :exclusions)
39
41
  def violations
40
42
  process_ast(sexps).
41
43
  select { |nesting, complexity| complexity > max_allowed_complexity }.
@@ -49,7 +51,9 @@ module Cane
49
51
  def process_ast(node, complexity = {}, nesting = [])
50
52
  if method_nodes.include?(node[0])
51
53
  nesting = nesting + [label_for(node)]
52
- complexity[nesting.join(" > ")] = calculate_abc(node)
54
+ unless excluded?(node, *nesting)
55
+ complexity[nesting.join(" > ")] = calculate_abc(node)
56
+ end
53
57
  elsif container_nodes.include?(node[0])
54
58
  parent = node[1][-1][1]
55
59
  nesting = nesting + [parent]
@@ -100,6 +104,14 @@ module Cane
100
104
  def condition_nodes
101
105
  [:==, :===, :"<>", :"<=", :">=", :"=~", :>, :<, :else, :"<=>"]
102
106
  end
107
+
108
+ METH_CHARS = { def: '#', defs: '.' }
109
+
110
+ def excluded?(node, *modules, meth_name)
111
+ meth_char = METH_CHARS.fetch(node.first)
112
+ description = [modules.join('::'), meth_name].join(meth_char)
113
+ exclusions.include?(description)
114
+ end
103
115
  end
104
116
 
105
117
  def file_names
@@ -113,5 +125,9 @@ module Cane
113
125
  def max_allowed_complexity
114
126
  opts.fetch(:max)
115
127
  end
128
+
129
+ def exclusions
130
+ opts.fetch(:exclusions, []).to_set
131
+ end
116
132
  end
117
133
  end
@@ -92,6 +92,8 @@ BANNER
92
92
 
93
93
  def add_cane_options
94
94
  add_option %w(--max-violations VALUE), "Max allowed violations"
95
+ add_option %w(--exclusions-file FILE),
96
+ "YAML file containing a list of exclusions"
95
97
 
96
98
  parser.separator ""
97
99
  end
@@ -1,3 +1,5 @@
1
+ require 'yaml'
2
+
1
3
  module Cane
2
4
  module CLI
3
5
 
@@ -18,15 +20,17 @@ module Cane
18
20
 
19
21
  def translate_abc_options(result)
20
22
  result[:abc] = {
21
- files: option_with_default(:abc_glob),
22
- max: option_with_default(:abc_max).to_i
23
+ files: option_with_default(:abc_glob),
24
+ max: option_with_default(:abc_max).to_i,
25
+ exclusions: exclusions_for('abc')
23
26
  } unless check_disabled(:no_abc, [:abc_glob, :abc_max])
24
27
  end
25
28
 
26
29
  def translate_style_options(result)
27
30
  result[:style] = {
28
- files: option_with_default(:style_glob),
29
- measure: option_with_default(:style_measure).to_i,
31
+ files: option_with_default(:style_glob),
32
+ measure: option_with_default(:style_measure).to_i,
33
+ exclusions: exclusions_for('style')
30
34
  } unless check_disabled(:no_style, [:style_glob])
31
35
  end
32
36
 
@@ -45,6 +49,22 @@ module Cane
45
49
  def option_with_default(key)
46
50
  options.fetch(key, defaults.fetch(key))
47
51
  end
52
+
53
+ private
54
+
55
+ def exclusions_for(tool)
56
+ Array(exclusions[tool])
57
+ end
58
+
59
+ def exclusions
60
+ @exclusions ||= begin
61
+ if file = options[:exclusions_file]
62
+ YAML.load_file(file)
63
+ else
64
+ {}
65
+ end
66
+ end
67
+ end
48
68
  end
49
69
 
50
70
  end
@@ -44,7 +44,7 @@ module Cane
44
44
  end
45
45
 
46
46
  def extract_class_name(line)
47
- line.match(/class (\S+)/)[1]
47
+ line.match(/class ([^\s;]+)/)[1]
48
48
  end
49
49
  end
50
50
 
@@ -32,6 +32,8 @@ module Cane
32
32
  attr_accessor :no_doc
33
33
  # Max violations to tolerate (default: 0)
34
34
  attr_accessor :max_violations
35
+ # File containing list of exclusions in YAML format
36
+ attr_accessor :exclusions_file
35
37
 
36
38
  # Add a threshold check. If the file exists and it contains a number,
37
39
  # compare that number with the given value using the operator.
@@ -63,7 +65,8 @@ module Cane
63
65
  :max_violations,
64
66
  :style_glob,
65
67
  :no_style,
66
- :style_measure
68
+ :style_measure,
69
+ :exclusions_file
67
70
  ].inject(threshold: @threshold) do |opts, setting|
68
71
  value = self.send(setting)
69
72
  opts[setting] = value unless value.nil?
@@ -1,11 +1,12 @@
1
1
  require 'cane/style_violation'
2
+ require 'set'
2
3
 
3
4
  module Cane
4
5
 
5
6
  # Creates violations for files that do not meet style conventions. Only
6
7
  # highly obvious, probable, and non-controversial checks are performed here.
7
8
  # It is not the goal of the tool to provide an extensive style report, but
8
- # only to prevent studid mistakes.
9
+ # only to prevent stupid mistakes.
9
10
  class StyleCheck < Struct.new(:opts)
10
11
  def violations
11
12
  file_list.map do |file_path|
@@ -30,7 +31,7 @@ module Cane
30
31
  end
31
32
 
32
33
  def file_list
33
- Dir[opts.fetch(:files)]
34
+ Dir[opts.fetch(:files)].reject { |f| excluded?(f) }
34
35
  end
35
36
 
36
37
  def measure
@@ -40,6 +41,14 @@ module Cane
40
41
  def map_lines(file_path, &block)
41
42
  File.open(file_path).each_line.map.with_index(&block)
42
43
  end
44
+
45
+ def exclusions
46
+ @exclusions ||= opts.fetch(:exclusions, []).to_set
47
+ end
48
+
49
+ def excluded?(file)
50
+ exclusions.include?(file)
51
+ end
43
52
  end
44
53
 
45
54
  end
@@ -1,3 +1,3 @@
1
1
  module Cane
2
- VERSION = '1.3.0'
2
+ VERSION = '1.4.0'
3
3
  end
@@ -11,11 +11,15 @@ module Cane
11
11
  grouped_violations.map do |description, violations|
12
12
  format_group_header(description, violations) +
13
13
  format_violations(violations)
14
- end.flatten.join("\n") + "\n\n"
14
+ end.flatten.join("\n") + "\n\n" + totals + "\n\n"
15
15
  end
16
16
 
17
17
  protected
18
18
 
19
+ def totals
20
+ "Total Violations: #{violations.count}"
21
+ end
22
+
19
23
  def format_group_header(description, violations)
20
24
  ["", "%s (%i):" % [description, violations.length], ""]
21
25
  end
@@ -55,6 +55,43 @@ describe Cane::AbcCheck do
55
55
  violations[0].description.should be_instance_of(String)
56
56
  end
57
57
 
58
+ it 'skips declared exclusions' do
59
+ file_name = make_file(<<-RUBY)
60
+ class Harness
61
+ def instance_meth
62
+ true
63
+ end
64
+
65
+ def self.class_meth
66
+ true
67
+ end
68
+
69
+ module Nested
70
+ def i_meth
71
+ true
72
+ end
73
+
74
+ def self.c_meth
75
+ true
76
+ end
77
+
78
+ def other_meth
79
+ true
80
+ end
81
+ end
82
+ end
83
+ RUBY
84
+
85
+ exclusions = %w[ Harness#instance_meth Harness.class_meth
86
+ Harness::Nested#i_meth Harness::Nested.c_meth ]
87
+ violations = described_class.new(files: file_name, max: 0,
88
+ exclusions: exclusions).violations
89
+ violations.length.should == 1
90
+ violations[0].should be_instance_of(Cane::AbcMaxViolation)
91
+ columns = violations[0].columns
92
+ columns.should == [file_name, "Harness > Nested > other_meth", 1]
93
+ end
94
+
58
95
  def self.it_should_extract_method_name(method_name, label=method_name)
59
96
  it "creates an AbcMaxViolation for #{method_name}" do
60
97
  file_name = make_file(<<-RUBY)
@@ -127,4 +127,37 @@ describe 'Cane' do
127
127
  run("--no-doc --doc-glob #{file_name}").should ==
128
128
  run("--doc-glob #{file_name}")
129
129
  end
130
+
131
+ it 'supports exclusions' do
132
+ line_with_whitespace = "whitespace "
133
+ file_name = make_file(<<-RUBY)
134
+ # #{line_with_whitespace}
135
+ class Harness
136
+ def complex_method(a)
137
+ if a < 2
138
+ return "low"
139
+ else
140
+ return "high"
141
+ end
142
+ end
143
+ end
144
+ RUBY
145
+
146
+ exclusions_file = make_file(<<-YAML)
147
+ abc:
148
+ - Harness#complex_method
149
+ style:
150
+ - #{file_name}
151
+ YAML
152
+
153
+ options = [
154
+ "--abc-glob", file_name,
155
+ "--abc-max", 1,
156
+ "--style-glob", file_name,
157
+ "--exclusions-file", exclusions_file
158
+ ].join(' ')
159
+
160
+ _, exitstatus = run(options)
161
+ exitstatus.should == 0
162
+ end
130
163
  end
@@ -22,9 +22,11 @@ end
22
22
  violations[0].should be_instance_of(Cane::UndocumentedClassViolation)
23
23
  violations[0].file_name.should == file_name
24
24
  violations[0].number.should == 3
25
+ violations[0].columns.last.should eq("NoDoc")
25
26
 
26
27
  violations[1].should be_instance_of(Cane::UndocumentedClassViolation)
27
28
  violations[1].file_name.should == file_name
28
29
  violations[1].number.should == 4
30
+ violations[1].columns.last.should eq("AlsoNoDoc")
29
31
  end
30
32
  end
@@ -3,16 +3,27 @@ require 'spec_helper'
3
3
  require 'cane/style_check'
4
4
 
5
5
  describe Cane::StyleCheck do
6
- it 'creates a StyleViolation for each method above the threshold' do
7
- ruby = [
6
+ let(:ruby_with_style_issue) do
7
+ [
8
8
  "def test ",
9
9
  "\t1",
10
10
  "end"
11
11
  ].join("\n")
12
- file_name = make_file(ruby)
12
+ end
13
+
14
+ it 'creates a StyleViolation for each method above the threshold' do
15
+ file_name = make_file(ruby_with_style_issue)
13
16
 
14
17
  violations = Cane::StyleCheck.new(files: file_name, measure: 80).violations
15
18
  violations.length.should == 2
16
19
  violations[0].should be_instance_of(StyleViolation)
17
20
  end
21
+
22
+ it 'skips declared exclusions' do
23
+ file_name = make_file(ruby_with_style_issue)
24
+
25
+ violations = Cane::StyleCheck.new(files: file_name, measure: 80,
26
+ exclusions: [file_name]).violations
27
+ violations.length.should == 0
28
+ end
18
29
  end
@@ -13,4 +13,10 @@ describe Cane::ViolationFormatter do
13
13
  it 'includes number of violations in the group header' do
14
14
  described_class.new([violation("FAIL")]).to_s.should include("(1)")
15
15
  end
16
+
17
+ it 'includes total number of violations' do
18
+ violations = [violation("FAIL1"),violation("FAIL2")]
19
+ result = described_class.new(violations).to_s
20
+ result.should include("Total Violations: 2")
21
+ end
16
22
  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: 1.3.0
4
+ version: 1.4.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: 2012-04-21 00:00:00.000000000 Z
12
+ date: 2012-07-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -114,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
114
  version: '0'
115
115
  requirements: []
116
116
  rubyforge_project:
117
- rubygems_version: 1.8.23
117
+ rubygems_version: 1.8.24
118
118
  signing_key:
119
119
  specification_version: 3
120
120
  summary: Fails your build if code quality thresholds are not met. Provides complexity