sugarcane 0.0.1
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 +7 -0
- data/HISTORY.md +126 -0
- data/LICENSE +14 -0
- data/README.md +235 -0
- data/bin/sugarcane +7 -0
- data/lib/cane.rb +4 -0
- data/lib/sugarcane/abc_check.rb +216 -0
- data/lib/sugarcane/cli/options.rb +30 -0
- data/lib/sugarcane/cli/parser.rb +191 -0
- data/lib/sugarcane/cli.rb +21 -0
- data/lib/sugarcane/default_checks.rb +17 -0
- data/lib/sugarcane/doc_check.rb +156 -0
- data/lib/sugarcane/encoding_aware_iterator.rb +30 -0
- data/lib/sugarcane/ext/array.rb +24 -0
- data/lib/sugarcane/file.rb +30 -0
- data/lib/sugarcane/json_formatter.rb +17 -0
- data/lib/sugarcane/menu.rb +228 -0
- data/lib/sugarcane/rake_task.rb +78 -0
- data/lib/sugarcane/runner.rb +59 -0
- data/lib/sugarcane/style_check.rb +91 -0
- data/lib/sugarcane/task_runner.rb +20 -0
- data/lib/sugarcane/threshold_check.rb +83 -0
- data/lib/sugarcane/version.rb +3 -0
- data/lib/sugarcane/violation_formatter.rb +75 -0
- data/spec/abc_check_spec.rb +164 -0
- data/spec/cane_spec.rb +136 -0
- data/spec/cli_spec.rb +23 -0
- data/spec/doc_check_spec.rb +163 -0
- data/spec/encoding_aware_iterator_spec.rb +32 -0
- data/spec/file_spec.rb +25 -0
- data/spec/json_formatter_spec.rb +10 -0
- data/spec/parser_spec.rb +161 -0
- data/spec/rake_task_spec.rb +68 -0
- data/spec/runner_spec.rb +23 -0
- data/spec/spec_helper.rb +71 -0
- data/spec/style_check_spec.rb +57 -0
- data/spec/threshold_check_spec.rb +101 -0
- data/spec/violation_formatter_spec.rb +38 -0
- data/sugarcane.gemspec +41 -0
- metadata +196 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'sugarcane/abc_check'
|
4
|
+
|
5
|
+
describe SugarCane::AbcCheck do
|
6
|
+
def check(file_name, opts = {})
|
7
|
+
described_class.new(opts.merge(abc_glob: file_name))
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'does not create violations when no_abc flag is set' do
|
11
|
+
file_name = make_file(<<-RUBY)
|
12
|
+
class Harness
|
13
|
+
def complex_method(a)
|
14
|
+
b = a
|
15
|
+
return b if b > 3
|
16
|
+
end
|
17
|
+
end
|
18
|
+
RUBY
|
19
|
+
|
20
|
+
violations = check(file_name, abc_max: 1, no_abc: true).violations
|
21
|
+
violations.should be_empty
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'creates an AbcMaxViolation for each method above the threshold' do
|
25
|
+
file_name = make_file(<<-RUBY)
|
26
|
+
class Harness
|
27
|
+
def not_complex
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def complex_method(a)
|
32
|
+
b = a
|
33
|
+
return b if b > 3
|
34
|
+
end
|
35
|
+
end
|
36
|
+
RUBY
|
37
|
+
|
38
|
+
violations = check(file_name, abc_max: 1, no_abc: false).violations
|
39
|
+
violations.length.should == 1
|
40
|
+
violations[0].values_at(:file, :label, :value).should ==
|
41
|
+
[file_name, "Harness#complex_method", 2]
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'sorts violations by complexity' do
|
45
|
+
file_name = make_file(<<-RUBY)
|
46
|
+
class Harness
|
47
|
+
def not_complex
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def complex_method(a)
|
52
|
+
b = a
|
53
|
+
return b if b > 3
|
54
|
+
end
|
55
|
+
end
|
56
|
+
RUBY
|
57
|
+
|
58
|
+
violations = check(file_name, abc_max: 0).violations
|
59
|
+
violations.length.should == 2
|
60
|
+
complexities = violations.map {|x| x[:value] }
|
61
|
+
complexities.should == complexities.sort.reverse
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'creates a violation when code cannot be parsed' do
|
65
|
+
file_name = make_file(<<-RUBY)
|
66
|
+
class Harness
|
67
|
+
RUBY
|
68
|
+
|
69
|
+
violations = check(file_name).violations
|
70
|
+
violations.length.should == 1
|
71
|
+
violations[0][:file].should == file_name
|
72
|
+
violations[0][:description].should be_instance_of(String)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'skips declared exclusions' do
|
76
|
+
file_name = make_file(<<-RUBY)
|
77
|
+
class Harness
|
78
|
+
def instance_meth
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.class_meth
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
module Nested
|
87
|
+
def i_meth
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.c_meth
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def other_meth
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
RUBY
|
101
|
+
|
102
|
+
exclusions = %w[ Harness#instance_meth Harness.class_meth
|
103
|
+
Harness::Nested#i_meth Harness::Nested.c_meth ]
|
104
|
+
violations = check(file_name,
|
105
|
+
abc_max: 0,
|
106
|
+
abc_exclude: exclusions
|
107
|
+
).violations
|
108
|
+
violations.length.should == 1
|
109
|
+
violations[0].values_at(:file, :label, :value).should ==
|
110
|
+
[file_name, "Harness::Nested#other_meth", 1]
|
111
|
+
end
|
112
|
+
|
113
|
+
it "creates an AbcMaxViolation for method in assigned anonymous class" do
|
114
|
+
file_name = make_file(<<-RUBY)
|
115
|
+
MyClass = Struct.new(:foo) do
|
116
|
+
def test_method(a)
|
117
|
+
b = a
|
118
|
+
return b if b > 3
|
119
|
+
end
|
120
|
+
end
|
121
|
+
RUBY
|
122
|
+
|
123
|
+
violations = check(file_name, abc_max: 1).violations
|
124
|
+
violations[0][:label] == "MyClass#test_method"
|
125
|
+
end
|
126
|
+
|
127
|
+
it "creates an AbcMaxViolation for method in anonymous class" do
|
128
|
+
file_name = make_file(<<-RUBY)
|
129
|
+
Class.new do
|
130
|
+
def test_method(a)
|
131
|
+
b = a
|
132
|
+
return b if b > 3
|
133
|
+
end
|
134
|
+
end
|
135
|
+
RUBY
|
136
|
+
|
137
|
+
violations = check(file_name, abc_max: 1).violations
|
138
|
+
violations[0][:label].should == "(anon)#test_method"
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.it_should_extract_method_name(name, label=name, sep='#')
|
142
|
+
it "creates an AbcMaxViolation for #{name}" do
|
143
|
+
file_name = make_file(<<-RUBY)
|
144
|
+
class Harness
|
145
|
+
def #{name}(a)
|
146
|
+
b = a
|
147
|
+
return b if b > 3
|
148
|
+
end
|
149
|
+
end
|
150
|
+
RUBY
|
151
|
+
|
152
|
+
violations = check(file_name, abc_max: 1).violations
|
153
|
+
violations[0][:label].should == "Harness#{sep}#{label}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# These method names all create different ASTs. Which is weird.
|
158
|
+
it_should_extract_method_name 'a'
|
159
|
+
it_should_extract_method_name 'self.a', 'a', '.'
|
160
|
+
it_should_extract_method_name 'next'
|
161
|
+
it_should_extract_method_name 'GET'
|
162
|
+
it_should_extract_method_name '`'
|
163
|
+
it_should_extract_method_name '>='
|
164
|
+
end
|
data/spec/cane_spec.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "stringio"
|
3
|
+
require 'sugarcane/cli'
|
4
|
+
require 'sugarcane/menu'
|
5
|
+
|
6
|
+
require 'sugarcane/rake_task'
|
7
|
+
require 'sugarcane/task_runner'
|
8
|
+
|
9
|
+
# Acceptance tests
|
10
|
+
describe 'The sugarcane application' do
|
11
|
+
let(:class_name) { "C#{rand(10 ** 10)}" }
|
12
|
+
|
13
|
+
let(:fn) do
|
14
|
+
make_file(<<-RUBY + " ")
|
15
|
+
class Harness
|
16
|
+
def complex_method(a)
|
17
|
+
if a < 2
|
18
|
+
return "low"
|
19
|
+
else
|
20
|
+
return "high"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
RUBY
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:check_file) do
|
28
|
+
make_file <<-RUBY
|
29
|
+
class #{class_name} < Struct.new(:opts)
|
30
|
+
def self.options
|
31
|
+
{
|
32
|
+
unhappy_file: ["File to check", default: [nil]]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def violations
|
37
|
+
[
|
38
|
+
description: "Files are unhappy",
|
39
|
+
file: opts.fetch(:unhappy_file),
|
40
|
+
label: ":("
|
41
|
+
]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
RUBY
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns a non-zero exit code and a details of checks that failed' do
|
48
|
+
output, exitstatus = run %(
|
49
|
+
--report
|
50
|
+
--style-glob #{fn}
|
51
|
+
--doc-glob #{fn}
|
52
|
+
--abc-glob #{fn}
|
53
|
+
--abc-max 1
|
54
|
+
-r #{check_file}
|
55
|
+
--check #{class_name}
|
56
|
+
--unhappy-file #{fn}
|
57
|
+
)
|
58
|
+
output.should include("Lines violated style requirements")
|
59
|
+
output.should include("Methods exceeded maximum allowed ABC complexity")
|
60
|
+
output.should include(
|
61
|
+
"Class and Module definitions require explanatory comments"
|
62
|
+
)
|
63
|
+
exitstatus.should == 1
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should not show methods within the expected ABC complexity' do
|
67
|
+
output, exitstatus = run %(
|
68
|
+
--report
|
69
|
+
--style-glob #{fn}
|
70
|
+
--doc-glob #{fn}
|
71
|
+
--abc-glob #{fn}
|
72
|
+
--abc-max 10
|
73
|
+
-r #{check_file}
|
74
|
+
--check #{class_name}
|
75
|
+
--unhappy-file #{fn}
|
76
|
+
)
|
77
|
+
output.should include("Lines violated style requirements")
|
78
|
+
output.should_not include("Methods exceeded maximum allowed ABC complexity")
|
79
|
+
output.should include(
|
80
|
+
"Class and Module definitions require explanatory comments"
|
81
|
+
)
|
82
|
+
exitstatus.should == 1
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'creates a menu' do
|
86
|
+
pending "Make a unit test or find a way to create artificial key presses"
|
87
|
+
output, exitstatus = run %(
|
88
|
+
--style-glob #{fn}
|
89
|
+
--doc-glob #{fn}
|
90
|
+
--abc-glob #{fn}
|
91
|
+
--abc-max 1
|
92
|
+
-r #{check_file}
|
93
|
+
--check #{class_name}
|
94
|
+
--unhappy-file #{fn}
|
95
|
+
)
|
96
|
+
|
97
|
+
exitstatus.should == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'handles invalid unicode input' do
|
101
|
+
fn = make_file("\xc3\x28")
|
102
|
+
|
103
|
+
_, exitstatus = run("--style-glob #{fn} --abc-glob #{fn} --doc-glob #{fn}")
|
104
|
+
|
105
|
+
exitstatus.should == 0
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'can run tasks in parallel' do
|
109
|
+
# This spec isn't great, but there is no good way to actually observe that
|
110
|
+
# tasks run in parallel and we want to verify the conditional is correct.
|
111
|
+
SugarCane.task_runner(parallel: true).should == Parallel
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'colorizes output' do
|
115
|
+
output, exitstatus = run("--report --color --abc-max 0")
|
116
|
+
|
117
|
+
output.should include("\e[31m")
|
118
|
+
end
|
119
|
+
|
120
|
+
after do
|
121
|
+
if Object.const_defined?(class_name)
|
122
|
+
Object.send(:remove_const, class_name)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def run(cli_args)
|
127
|
+
result = nil
|
128
|
+
output = capture_stdout do
|
129
|
+
result = SugarCane::CLI.run(
|
130
|
+
%w(--no-abc --no-style --no-doc) + cli_args.split(/\s+/m)
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
[output, result ? 0 : 1]
|
135
|
+
end
|
136
|
+
end
|
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sugarcane/cli'
|
3
|
+
|
4
|
+
describe SugarCane::CLI do
|
5
|
+
describe '.run' do
|
6
|
+
|
7
|
+
let!(:parser) { class_double("SugarCane::CLI::Parser").as_stubbed_const }
|
8
|
+
let!(:cane) { class_double("SugarCane").as_stubbed_const }
|
9
|
+
|
10
|
+
it 'runs SugarCane with the given arguments' do
|
11
|
+
parser.should_receive(:parse).with("--args").and_return(args: true)
|
12
|
+
cane.should_receive(:run).with(args: true).and_return("tracer")
|
13
|
+
|
14
|
+
described_class.run("--args").should == "tracer"
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does not run SugarCane if parser was able to handle input' do
|
18
|
+
parser.should_receive(:parse).with("--args").and_return("tracer")
|
19
|
+
|
20
|
+
described_class.run("--args").should == "tracer"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sugarcane/doc_check'
|
3
|
+
|
4
|
+
describe SugarCane::DocCheck do
|
5
|
+
def check(file_name, opts = {})
|
6
|
+
described_class.new(opts.merge(doc_glob: file_name))
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'creates a DocViolation for each undocumented class with a method' do
|
10
|
+
file_name = make_file <<-RUBY
|
11
|
+
class Doc; end
|
12
|
+
class Empty; end # No doc is fine
|
13
|
+
class NoDoc; def with_method; end; end
|
14
|
+
classIgnore = nil
|
15
|
+
[:class]
|
16
|
+
# class Ignore
|
17
|
+
class Meta
|
18
|
+
class << self; end
|
19
|
+
end
|
20
|
+
module DontNeedDoc; end
|
21
|
+
# This module is documented
|
22
|
+
module HasDoc
|
23
|
+
def mixin; end
|
24
|
+
end
|
25
|
+
module AlsoNeedsDoc; def mixin; end; end
|
26
|
+
module NoDocIsFine
|
27
|
+
module ButThisNeedsDoc
|
28
|
+
def self.global
|
29
|
+
end
|
30
|
+
end
|
31
|
+
module AlsoNoDocIsFine; end
|
32
|
+
# We've got docs
|
33
|
+
module CauseWeNeedThem
|
34
|
+
def mixin
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
|
40
|
+
violations = check(file_name).violations
|
41
|
+
violations.length.should == 3
|
42
|
+
|
43
|
+
violations[0].values_at(:file, :line, :label).should == [
|
44
|
+
file_name, 3, "NoDoc"
|
45
|
+
]
|
46
|
+
|
47
|
+
violations[1].values_at(:file, :line, :label).should == [
|
48
|
+
file_name, 15, "AlsoNeedsDoc"
|
49
|
+
]
|
50
|
+
|
51
|
+
violations[2].values_at(:file, :line, :label).should == [
|
52
|
+
file_name, 17, "ButThisNeedsDoc"
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'does not create violations for single line classes without methods' do
|
57
|
+
file_name = make_file <<-RUBY
|
58
|
+
class NeedsDoc
|
59
|
+
class AlsoNeedsDoc < StandardError; def foo; end; end
|
60
|
+
class NoDocIsOk < StandardError; end
|
61
|
+
class NoDocIsAlsoOk < StandardError; end # No doc is fine on this too
|
62
|
+
|
63
|
+
def my_method
|
64
|
+
end
|
65
|
+
end
|
66
|
+
RUBY
|
67
|
+
|
68
|
+
violations = check(file_name).violations
|
69
|
+
violations.length.should == 2
|
70
|
+
|
71
|
+
violations[0].values_at(:file, :line, :label).should == [
|
72
|
+
file_name, 1, "NeedsDoc"
|
73
|
+
]
|
74
|
+
|
75
|
+
violations[1].values_at(:file, :line, :label).should == [
|
76
|
+
file_name, 2, "AlsoNeedsDoc"
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'ignores magic encoding comments' do
|
81
|
+
file_name = make_file <<-RUBY
|
82
|
+
# coding = utf-8
|
83
|
+
class NoDoc; def do_stuff; end; end
|
84
|
+
# -*- encoding : utf-8 -*-
|
85
|
+
class AlsoNoDoc; def do_more_stuff; end; end
|
86
|
+
# Parse a Transfer-Encoding: Chunked response
|
87
|
+
class Doc; end
|
88
|
+
RUBY
|
89
|
+
|
90
|
+
violations = check(file_name).violations
|
91
|
+
violations.length.should == 2
|
92
|
+
|
93
|
+
violations[0].values_at(:file, :line, :label).should == [
|
94
|
+
file_name, 2, "NoDoc"
|
95
|
+
]
|
96
|
+
violations[1].values_at(:file, :line, :label).should == [
|
97
|
+
file_name, 4, "AlsoNoDoc"
|
98
|
+
]
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'creates a violation for missing README' do
|
102
|
+
file = class_double("SugarCane::File").as_stubbed_const
|
103
|
+
stub_const("SugarCane::File", file)
|
104
|
+
file.should_receive(:case_insensitive_glob).with("README*").and_return([])
|
105
|
+
|
106
|
+
violations = check("").violations
|
107
|
+
violations.length.should == 1
|
108
|
+
|
109
|
+
violations[0].values_at(:description, :label).should == [
|
110
|
+
"Missing documentation", "No README found"
|
111
|
+
]
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'does not create a violation when readme exists' do
|
115
|
+
file = class_double("SugarCane::File").as_stubbed_const
|
116
|
+
stub_const("SugarCane::File", file)
|
117
|
+
file
|
118
|
+
.should_receive(:case_insensitive_glob)
|
119
|
+
.with("README*")
|
120
|
+
.and_return(%w(readme.md))
|
121
|
+
|
122
|
+
violations = check("").violations
|
123
|
+
violations.length.should == 0
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'skips declared exclusions' do
|
127
|
+
file_name = make_file <<-FILE.gsub /^\s{6}/, ''
|
128
|
+
class NeedsDocumentation
|
129
|
+
end
|
130
|
+
FILE
|
131
|
+
|
132
|
+
violations = check(file_name,
|
133
|
+
doc_exclude: [file_name]
|
134
|
+
).violations
|
135
|
+
|
136
|
+
violations.length.should == 0
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'skips declared glob-based exclusions' do
|
140
|
+
file_name = make_file <<-FILE.gsub /^\s{6}/, ''
|
141
|
+
class NeedsDocumentation
|
142
|
+
end
|
143
|
+
FILE
|
144
|
+
|
145
|
+
violations = check(file_name,
|
146
|
+
doc_exclude: ["#{File.dirname(file_name)}/*"]
|
147
|
+
).violations
|
148
|
+
|
149
|
+
violations.length.should == 0
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'skips class inside an array' do
|
153
|
+
file_name = make_file <<-RUBY
|
154
|
+
%w(
|
155
|
+
class
|
156
|
+
method
|
157
|
+
)
|
158
|
+
RUBY
|
159
|
+
|
160
|
+
violations = check(file_name).violations
|
161
|
+
violations.length.should == 0
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'sugarcane/encoding_aware_iterator'
|
4
|
+
|
5
|
+
# Example bad input from:
|
6
|
+
# http://stackoverflow.com/questions/1301402/example-invalid-utf8-string
|
7
|
+
describe SugarCane::EncodingAwareIterator do
|
8
|
+
it 'handles non-UTF8 input' do
|
9
|
+
lines = ["\xc3\x28"]
|
10
|
+
result = described_class.new(lines).map.with_index do |line, number|
|
11
|
+
line.should be_kind_of(String)
|
12
|
+
[line =~ /\s/, number]
|
13
|
+
end
|
14
|
+
result.should == [[nil, 0]]
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does not enter an infinite loop on persistently bad input' do
|
18
|
+
->{
|
19
|
+
described_class.new([""]).map.with_index do |line, number|
|
20
|
+
"\xc3\x28" =~ /\s/
|
21
|
+
end
|
22
|
+
}.should raise_error(ArgumentError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'allows each with no block' do
|
26
|
+
called_with_line = nil
|
27
|
+
described_class.new([""]).each.with_index do |line, number|
|
28
|
+
called_with_line = line
|
29
|
+
end
|
30
|
+
called_with_line.should == ""
|
31
|
+
end
|
32
|
+
end
|
data/spec/file_spec.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
require 'sugarcane/file'
|
5
|
+
|
6
|
+
describe SugarCane::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
|
+
SugarCane::File.case_insensitive_glob("README*").should =~ expected
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sugarcane/json_formatter'
|
3
|
+
|
4
|
+
describe SugarCane::JsonFormatter do
|
5
|
+
it 'outputs violations as JSON' do
|
6
|
+
violations = [{description: 'Fail', line: 3}]
|
7
|
+
JSON.parse(described_class.new(violations).to_s).should ==
|
8
|
+
[{'description' => 'Fail', 'line' => 3}]
|
9
|
+
end
|
10
|
+
end
|