yardcheck 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +214 -0
- data/.rubocop_todo.yml +69 -0
- data/.ruby-version +1 -0
- data/Gemfile +22 -0
- data/Guardfile +5 -0
- data/README.md +83 -0
- data/bin/yardcheck +6 -0
- data/lib/yardcheck.rb +26 -0
- data/lib/yardcheck/color.rb +27 -0
- data/lib/yardcheck/const.rb +39 -0
- data/lib/yardcheck/documentation.rb +29 -0
- data/lib/yardcheck/documentation/method_object.rb +111 -0
- data/lib/yardcheck/method_call.rb +38 -0
- data/lib/yardcheck/method_tracer.rb +86 -0
- data/lib/yardcheck/observation.rb +72 -0
- data/lib/yardcheck/proxy.rb +33 -0
- data/lib/yardcheck/runner.rb +93 -0
- data/lib/yardcheck/source_lines.rb +29 -0
- data/lib/yardcheck/spec_observer.rb +28 -0
- data/lib/yardcheck/test_runner.rb +27 -0
- data/lib/yardcheck/test_value.rb +82 -0
- data/lib/yardcheck/typedef.rb +122 -0
- data/lib/yardcheck/typedef/parser.rb +82 -0
- data/lib/yardcheck/version.rb +5 -0
- data/lib/yardcheck/violation.rb +156 -0
- data/lib/yardcheck/warning.rb +14 -0
- data/spec/integration/yardcheck_spec.rb +57 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/unit/yardcheck/const_spec.rb +48 -0
- data/spec/unit/yardcheck/documentation_spec.rb +148 -0
- data/spec/unit/yardcheck/method_tracer_spec.rb +59 -0
- data/spec/unit/yardcheck/runner_spec.rb +183 -0
- data/spec/unit/yardcheck/test_value_spec.rb +25 -0
- data/spec/unit/yardcheck/typedef/parser_spec.rb +37 -0
- data/spec/unit/yardcheck/typedef_spec.rb +32 -0
- data/test_app/.rspec +1 -0
- data/test_app/Gemfile +7 -0
- data/test_app/Gemfile.lock +66 -0
- data/test_app/lib/test_app.rb +119 -0
- data/test_app/spec/test_app_spec.rb +49 -0
- data/yardcheck.gemspec +24 -0
- metadata +158 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Yardcheck::MethodTracer do
|
4
|
+
it 'traces method calls and returns for a namespace' do
|
5
|
+
stub_const('Foo', Class.new)
|
6
|
+
stub_const('Qux', Class.new)
|
7
|
+
|
8
|
+
class Foo
|
9
|
+
def self.singleton_method_example(baz)
|
10
|
+
baz.upcase
|
11
|
+
end
|
12
|
+
|
13
|
+
def instance_method_example(baz)
|
14
|
+
Foo.singleton_method_example(baz)
|
15
|
+
end
|
16
|
+
end # Foo
|
17
|
+
|
18
|
+
class Qux
|
19
|
+
def self.singleton_method_example(baz)
|
20
|
+
baz.upcase
|
21
|
+
end
|
22
|
+
|
23
|
+
def instance_method_example(baz)
|
24
|
+
Qux.singleton_method_example(baz)
|
25
|
+
end
|
26
|
+
end # Qux
|
27
|
+
|
28
|
+
tracer = described_class.new(Foo)
|
29
|
+
foo = Foo.new # Capture the activity for this object
|
30
|
+
qux = Qux.new # Ignore this one
|
31
|
+
str = 'Hello'
|
32
|
+
|
33
|
+
tracer.trace do
|
34
|
+
foo.instance_method_example(str)
|
35
|
+
qux.instance_method_example(str)
|
36
|
+
end
|
37
|
+
|
38
|
+
expect(tracer.events).to eq([
|
39
|
+
Yardcheck::MethodCall.process(
|
40
|
+
scope: :class,
|
41
|
+
selector: :singleton_method_example,
|
42
|
+
namespace: Foo.singleton_class,
|
43
|
+
params: { baz: 'Hello' },
|
44
|
+
return_value: 'HELLO',
|
45
|
+
example_location: RSpec.current_example.location,
|
46
|
+
error_raised: false
|
47
|
+
),
|
48
|
+
Yardcheck::MethodCall.process(
|
49
|
+
scope: :instance,
|
50
|
+
selector: :instance_method_example,
|
51
|
+
namespace: Foo,
|
52
|
+
params: { baz: 'Hello' },
|
53
|
+
return_value: 'HELLO',
|
54
|
+
example_location: RSpec.current_example.location,
|
55
|
+
error_raised: false
|
56
|
+
)
|
57
|
+
])
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Yardcheck::Runner do
|
4
|
+
subject(:runner) do
|
5
|
+
described_class.new(observer.associate_with(docs), io)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:observer) { Yardcheck::SpecObserver.new(observed_events) }
|
9
|
+
let(:docs) { Yardcheck::Documentation.new(yardocs) }
|
10
|
+
let(:io) { StringIO.new }
|
11
|
+
|
12
|
+
let(:yardocs) do
|
13
|
+
YardcheckSpec::YARDOCS.each do |yardoc|
|
14
|
+
allow(yardoc).to receive(:file).and_wrap_original do |method|
|
15
|
+
File.join('./test_app', method.call).to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:observed_events) do
|
21
|
+
[
|
22
|
+
Yardcheck::MethodCall.process(
|
23
|
+
scope: :instance,
|
24
|
+
selector: :add,
|
25
|
+
namespace: TestApp::Namespace,
|
26
|
+
params: { left: 'foo', right: 3 },
|
27
|
+
return_value: 5,
|
28
|
+
example_location: 'test_app_spec.rb:1',
|
29
|
+
error_raised: false
|
30
|
+
),
|
31
|
+
Yardcheck::MethodCall.process(
|
32
|
+
scope: :class,
|
33
|
+
selector: :add,
|
34
|
+
namespace: TestApp::Namespace.singleton_class,
|
35
|
+
params: { left: 2, right: 3 },
|
36
|
+
return_value: 5,
|
37
|
+
example_location: 'test_app_spec.rb:2',
|
38
|
+
error_raised: false
|
39
|
+
)
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:output) do
|
44
|
+
<<~MSG
|
45
|
+
Expected #<Class:TestApp::Namespace>#add to return String but observed Fixnum
|
46
|
+
|
47
|
+
source: ./test_app/lib/test_app.rb:9
|
48
|
+
tests:
|
49
|
+
- test_app_spec.rb:2
|
50
|
+
|
51
|
+
# Singleton method with correct param definition and incorrect return
|
52
|
+
#
|
53
|
+
# @param left [Integer]
|
54
|
+
# @param right [Integer]
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
def self.add(left, right)
|
58
|
+
left + right
|
59
|
+
end
|
60
|
+
|
61
|
+
Expected TestApp::Namespace#add to receive Integer for left but observed String
|
62
|
+
|
63
|
+
source: ./test_app/lib/test_app.rb:19
|
64
|
+
tests:
|
65
|
+
- test_app_spec.rb:1
|
66
|
+
|
67
|
+
# Instance method with correct param definition and incorrect return
|
68
|
+
#
|
69
|
+
# @param left [Integer]
|
70
|
+
# @param right [Integer]
|
71
|
+
#
|
72
|
+
# @return [String]
|
73
|
+
def add(left, right)
|
74
|
+
left + right
|
75
|
+
end
|
76
|
+
|
77
|
+
Expected TestApp::Namespace#add to return String but observed Fixnum
|
78
|
+
|
79
|
+
source: ./test_app/lib/test_app.rb:19
|
80
|
+
tests:
|
81
|
+
- test_app_spec.rb:1
|
82
|
+
|
83
|
+
# Instance method with correct param definition and incorrect return
|
84
|
+
#
|
85
|
+
# @param left [Integer]
|
86
|
+
# @param right [Integer]
|
87
|
+
#
|
88
|
+
# @return [String]
|
89
|
+
def add(left, right)
|
90
|
+
left + right
|
91
|
+
end
|
92
|
+
|
93
|
+
MSG
|
94
|
+
end
|
95
|
+
|
96
|
+
def remove_color(string)
|
97
|
+
string.gsub(/\e\[(?:1\;)?\d+m/, '')
|
98
|
+
end
|
99
|
+
|
100
|
+
shared_examples 'violation output' do
|
101
|
+
it 'compares the spec observations against the documentation' do
|
102
|
+
runner.check
|
103
|
+
|
104
|
+
expect(remove_color(io.string)).to eql(output)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
include_examples 'violation output'
|
109
|
+
|
110
|
+
context 'when multiple tests find the same violation' do
|
111
|
+
let(:observed_events) do
|
112
|
+
[
|
113
|
+
Yardcheck::MethodCall.process(
|
114
|
+
scope: :instance,
|
115
|
+
selector: :add,
|
116
|
+
namespace: TestApp::Namespace,
|
117
|
+
params: { left: 'foo', right: 3 },
|
118
|
+
return_value: 'valid return type',
|
119
|
+
example_location: 'test_app_spec.rb:1',
|
120
|
+
error_raised: false
|
121
|
+
),
|
122
|
+
Yardcheck::MethodCall.process(
|
123
|
+
scope: :instance,
|
124
|
+
selector: :add,
|
125
|
+
namespace: TestApp::Namespace,
|
126
|
+
params: { left: 'foo', right: 3 },
|
127
|
+
return_value: 'valid return type',
|
128
|
+
example_location: 'test_app_spec.rb:2',
|
129
|
+
error_raised: false
|
130
|
+
),
|
131
|
+
Yardcheck::MethodCall.process(
|
132
|
+
scope: :instance,
|
133
|
+
selector: :add,
|
134
|
+
namespace: TestApp::Namespace,
|
135
|
+
params: { left: 1, right: 'now this one is wrong' },
|
136
|
+
return_value: 'valid return type',
|
137
|
+
example_location: 'test_app_spec.rb:3',
|
138
|
+
error_raised: false
|
139
|
+
)
|
140
|
+
]
|
141
|
+
end
|
142
|
+
|
143
|
+
let(:output) do
|
144
|
+
<<~MSG
|
145
|
+
Expected TestApp::Namespace#add to receive Integer for left but observed String
|
146
|
+
|
147
|
+
source: ./test_app/lib/test_app.rb:19
|
148
|
+
tests:
|
149
|
+
- test_app_spec.rb:1
|
150
|
+
- test_app_spec.rb:2
|
151
|
+
|
152
|
+
# Instance method with correct param definition and incorrect return
|
153
|
+
#
|
154
|
+
# @param left [Integer]
|
155
|
+
# @param right [Integer]
|
156
|
+
#
|
157
|
+
# @return [String]
|
158
|
+
def add(left, right)
|
159
|
+
left + right
|
160
|
+
end
|
161
|
+
|
162
|
+
Expected TestApp::Namespace#add to receive Integer for right but observed String
|
163
|
+
|
164
|
+
source: ./test_app/lib/test_app.rb:19
|
165
|
+
tests:
|
166
|
+
- test_app_spec.rb:3
|
167
|
+
|
168
|
+
# Instance method with correct param definition and incorrect return
|
169
|
+
#
|
170
|
+
# @param left [Integer]
|
171
|
+
# @param right [Integer]
|
172
|
+
#
|
173
|
+
# @return [String]
|
174
|
+
def add(left, right)
|
175
|
+
left + right
|
176
|
+
end
|
177
|
+
|
178
|
+
MSG
|
179
|
+
end
|
180
|
+
|
181
|
+
include_examples 'violation output'
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Yardcheck::TestValue do
|
4
|
+
it 'just wraps normal values' do
|
5
|
+
expect(described_class.process(1)).to eql(described_class.new(1))
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'extracts the namespace from instance doubles' do
|
9
|
+
expect(described_class.process(instance_double(String))).to eql(
|
10
|
+
described_class::InstanceDouble.new(String)
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'extracts the name from doubles' do
|
15
|
+
expect(described_class.process(double(:foo))).to eql(
|
16
|
+
described_class::Double.new(:foo)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'handles anonymous doubles' do
|
21
|
+
expect(described_class.process(double)).to eql(
|
22
|
+
described_class::Double.new('(anonymous)')
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Yardcheck::Typedef::Parser do
|
4
|
+
def parse(*types)
|
5
|
+
described_class.new('TestApp::Namespace', types).parse
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'resolves one normal constant' do
|
9
|
+
expect(parse('Integer')).to eql(Yardcheck::Typedef.new([
|
10
|
+
Yardcheck::Typedef::Literal.new(Yardcheck::Const.new(Integer))
|
11
|
+
]))
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'resolves multiple constants' do
|
15
|
+
expect(parse('Integer', 'String')).to eql(Yardcheck::Typedef.new([
|
16
|
+
Yardcheck::Typedef::Literal.new(Yardcheck::Const.new(Integer)),
|
17
|
+
Yardcheck::Typedef::Literal.new(Yardcheck::Const.new(String))
|
18
|
+
]))
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'resolves child of namespace' do
|
22
|
+
expect(parse('Child')).to eql(Yardcheck::Typedef.new([
|
23
|
+
Yardcheck::Typedef::Literal.new(Yardcheck::Const.new(TestApp::Namespace::Child))
|
24
|
+
]))
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'handles array of items' do
|
28
|
+
expect(parse('Array<String>')).to eql(
|
29
|
+
Yardcheck::Typedef.new([
|
30
|
+
Yardcheck::Typedef::Collection.new(
|
31
|
+
Yardcheck::Const.new(Array),
|
32
|
+
[Yardcheck::Typedef::Literal.new(Yardcheck::Const.new(String))]
|
33
|
+
)
|
34
|
+
])
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Yardcheck::Typedef do
|
4
|
+
def typedef(*types)
|
5
|
+
typedefs =
|
6
|
+
types.map do |type|
|
7
|
+
described_class::Literal.new(Yardcheck::Const.new(type))
|
8
|
+
end
|
9
|
+
|
10
|
+
described_class.new(typedefs)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'matches exact type matches' do
|
14
|
+
expect(typedef(Integer).match?(Yardcheck::TestValue.new(1))).to be(true)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'matches descendants' do
|
18
|
+
parent = Class.new
|
19
|
+
child = Class.new(parent)
|
20
|
+
|
21
|
+
expect(typedef(parent).match?(Yardcheck::TestValue.new(child.new))).to be(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'matches union type definitions' do
|
25
|
+
aggregate_failures do
|
26
|
+
definition = typedef(Integer, String)
|
27
|
+
expect(definition.match?(Yardcheck::TestValue.new(1))).to be(true)
|
28
|
+
expect(definition.match?(Yardcheck::TestValue.new('hi'))).to be(true)
|
29
|
+
expect(definition.match?(Yardcheck::TestValue.new(:hi))).to be(false)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/test_app/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/test_app/Gemfile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
yardcheck (0.0.0)
|
5
|
+
anima
|
6
|
+
coderay (~> 1.1)
|
7
|
+
concord
|
8
|
+
rspec
|
9
|
+
yard (~> 0.9)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
abstract_type (0.0.7)
|
15
|
+
adamantium (0.2.0)
|
16
|
+
ice_nine (~> 0.11.0)
|
17
|
+
memoizable (~> 0.4.0)
|
18
|
+
anima (0.3.0)
|
19
|
+
abstract_type (~> 0.0.7)
|
20
|
+
adamantium (~> 0.2)
|
21
|
+
equalizer (~> 0.0.11)
|
22
|
+
byebug (9.0.6)
|
23
|
+
coderay (1.1.1)
|
24
|
+
concord (0.1.5)
|
25
|
+
adamantium (~> 0.2.0)
|
26
|
+
equalizer (~> 0.0.9)
|
27
|
+
diff-lcs (1.2.5)
|
28
|
+
equalizer (0.0.11)
|
29
|
+
ice_nine (0.11.2)
|
30
|
+
memoizable (0.4.2)
|
31
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
32
|
+
method_source (0.8.2)
|
33
|
+
pry (0.10.4)
|
34
|
+
coderay (~> 1.1.0)
|
35
|
+
method_source (~> 0.8.1)
|
36
|
+
slop (~> 3.4)
|
37
|
+
pry-byebug (3.4.2)
|
38
|
+
byebug (~> 9.0)
|
39
|
+
pry (~> 0.10)
|
40
|
+
rspec (3.5.0)
|
41
|
+
rspec-core (~> 3.5.0)
|
42
|
+
rspec-expectations (~> 3.5.0)
|
43
|
+
rspec-mocks (~> 3.5.0)
|
44
|
+
rspec-core (3.5.4)
|
45
|
+
rspec-support (~> 3.5.0)
|
46
|
+
rspec-expectations (3.5.0)
|
47
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
+
rspec-support (~> 3.5.0)
|
49
|
+
rspec-mocks (3.5.0)
|
50
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
51
|
+
rspec-support (~> 3.5.0)
|
52
|
+
rspec-support (3.5.0)
|
53
|
+
slop (3.6.0)
|
54
|
+
thread_safe (0.3.6)
|
55
|
+
yard (0.9.8)
|
56
|
+
|
57
|
+
PLATFORMS
|
58
|
+
ruby
|
59
|
+
|
60
|
+
DEPENDENCIES
|
61
|
+
pry
|
62
|
+
pry-byebug
|
63
|
+
yardcheck!
|
64
|
+
|
65
|
+
BUNDLED WITH
|
66
|
+
1.14.6
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module TestApp
|
2
|
+
class Namespace
|
3
|
+
# Singleton method with correct param definition and incorrect return
|
4
|
+
#
|
5
|
+
# @param left [Integer]
|
6
|
+
# @param right [Integer]
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
def self.add(left, right)
|
10
|
+
left + right
|
11
|
+
end
|
12
|
+
|
13
|
+
# Instance method with correct param definition and incorrect return
|
14
|
+
#
|
15
|
+
# @param left [Integer]
|
16
|
+
# @param right [Integer]
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
def add(left, right)
|
20
|
+
left + right
|
21
|
+
end
|
22
|
+
|
23
|
+
# Untested method with documentation
|
24
|
+
#
|
25
|
+
# @param str [String]
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
def untested_method(str)
|
29
|
+
end
|
30
|
+
|
31
|
+
def undocumented
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param foo [What]
|
35
|
+
#
|
36
|
+
# @return [Wow]
|
37
|
+
def ignoring_invalid_types(foo)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [TestApp::Namespace::Parent]
|
41
|
+
def returns_generic
|
42
|
+
Child.new
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Child]
|
46
|
+
def documents_relative
|
47
|
+
'str'
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return no type specified here
|
51
|
+
def return_tag_without_type
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param [String]
|
55
|
+
def param_without_name(unnamed)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [nil]
|
59
|
+
def return_nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Namespace]
|
63
|
+
def return_self
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [undefined]
|
68
|
+
def undefined_return
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Boolean]
|
72
|
+
def bool_return
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Array<String>]
|
76
|
+
def array_return
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [String] in some cases
|
80
|
+
# @return [nil] otherwise
|
81
|
+
def multiple_returns
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param list [Enumerable<Integer>]
|
85
|
+
# @return [nil]
|
86
|
+
def enumerable_param(list)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param value [String]
|
90
|
+
# @return [nil]
|
91
|
+
def properly_tested_with_instance_double(value)
|
92
|
+
end
|
93
|
+
|
94
|
+
# @param value [String]
|
95
|
+
# @return [nil]
|
96
|
+
def improperly_tested_with_instance_double(value)
|
97
|
+
end
|
98
|
+
|
99
|
+
AppError = Class.new(StandardError)
|
100
|
+
|
101
|
+
# @return [Fixnum]
|
102
|
+
def always_raise
|
103
|
+
raise AppError
|
104
|
+
|
105
|
+
1
|
106
|
+
end
|
107
|
+
|
108
|
+
# @return [:foo]
|
109
|
+
def returns_literal_symbol
|
110
|
+
:foo
|
111
|
+
end
|
112
|
+
|
113
|
+
class Parent
|
114
|
+
end
|
115
|
+
|
116
|
+
class Child < Parent
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|