rubocop-cask 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ require 'forwardable'
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Cask
6
+ # This cop checks that a cask's stanzas are ordered correctly.
7
+ # See https://github.com/caskroom/homebrew-cask/blob/master/CONTRIBUTING.md#stanza-order
8
+ # for more info.
9
+ class StanzaOrder < Cop
10
+ extend Forwardable
11
+ include RuboCop::Cask::CaskHelp
12
+
13
+ MESSAGE = '`%s` stanza out of order'
14
+
15
+ def on_cask(cask_node)
16
+ @cask_block = RuboCop::Cask::AST::CaskBlock.new(cask_node)
17
+ add_offenses
18
+ end
19
+
20
+ def autocorrect(stanza)
21
+ lambda do |corrector|
22
+ correct_stanza_index = toplevel_stanzas.index(stanza)
23
+ correct_stanza = sorted_toplevel_stanzas[correct_stanza_index]
24
+ corrector.replace(stanza.expression, correct_stanza.source)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :cask_block
31
+ def_delegators :cask_block, :cask_node, :toplevel_stanzas,
32
+ :sorted_toplevel_stanzas
33
+
34
+ def add_offenses
35
+ offending_stanzas.each do |stanza|
36
+ message = format(MESSAGE, stanza.stanza_name)
37
+ add_offense(stanza, stanza.expression, message)
38
+ end
39
+ end
40
+
41
+ def offending_stanzas
42
+ offending = []
43
+ toplevel_stanzas.each_with_index do |stanza, i|
44
+ offending << stanza unless stanza == sorted_toplevel_stanzas[i]
45
+ end
46
+ offending
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,105 +1,84 @@
1
1
  describe RuboCop::Cop::Cask::NoDslVersion do
2
+ include CopSharedExamples
3
+
2
4
  subject(:cop) { described_class.new }
3
5
 
4
6
  context 'with header method `cask`' do
7
+ let(:header_method) { 'cask' }
8
+
5
9
  context 'with no dsl version' do
6
- it 'passes' do
7
- inspect_source(cop, [
8
- "cask 'foo' do",
9
- 'end'
10
- ])
11
- expect(cop.offenses).to be_empty
12
- end
10
+ let(:source) { "cask 'foo' do; end" }
11
+
12
+ include_examples 'does not report any offenses'
13
13
  end
14
14
 
15
15
  context 'with dsl version' do
16
- it 'fails' do
17
- inspect_source(cop, [
18
- "cask :v1 => 'foo' do",
19
- 'end'
20
- ])
21
- expect(cop.offenses.size).to eq(1)
22
- expect(cop.offenses.first.line).to eq(1)
23
- expect(cop.messages)
24
- .to eq(["Use `cask 'foo'` instead of `cask :v1 => 'foo'`"])
25
- expect(cop.highlights)
26
- .to eq(["cask :v1 => 'foo'"])
16
+ let(:source) { "cask :v1 => 'foo' do; end" }
17
+ let(:correct_source) { "cask 'foo' do; end" }
18
+ let(:expected_offenses) do
19
+ [
20
+ {
21
+ message: "Use `cask 'foo'` instead of `cask :v1 => 'foo'`",
22
+ severity: :convention,
23
+ line: 1,
24
+ column: 0,
25
+ source: "cask :v1 => 'foo'"
26
+ }
27
+ ]
27
28
  end
28
29
 
29
- it 'autocorrects' do
30
- new_source = autocorrect_source(cop, [
31
- "cask :v1 => 'foo' do",
32
- 'end'
33
- ])
34
- expect(new_source).to eq([
35
- "cask 'foo' do",
36
- 'end'
37
- ].join("\n"))
38
- end
30
+ include_examples 'reports offenses'
31
+
32
+ include_examples 'autocorrects offenses'
39
33
  end
40
34
 
41
35
  context 'with dsl version containing "test"' do
42
- it 'fails' do
43
- inspect_source(cop, [
44
- "cask :v1test => 'foo' do",
45
- 'end'
46
- ])
47
- expect(cop.offenses.size).to eq(1)
48
- expect(cop.offenses.first.line).to eq(1)
49
- expect(cop.messages)
50
- .to eq(["Use `test_cask 'foo'` instead of `cask :v1test => 'foo'`"])
51
- expect(cop.highlights)
52
- .to eq(["cask :v1test => 'foo'"])
36
+ let(:source) { "cask :v1test => 'foo' do; end" }
37
+ let(:correct_source) { "test_cask 'foo' do; end" }
38
+ let(:expected_offenses) do
39
+ [
40
+ {
41
+ message: "Use `test_cask 'foo'` instead of `cask :v1test => 'foo'`",
42
+ severity: :convention,
43
+ line: 1,
44
+ column: 0,
45
+ source: "cask :v1test => 'foo'"
46
+ }
47
+ ]
53
48
  end
54
49
 
55
- it 'autocorrects' do
56
- new_source = autocorrect_source(cop, [
57
- "cask :v1test => 'foo' do",
58
- 'end'
59
- ])
60
- expect(new_source).to eq([
61
- "test_cask 'foo' do",
62
- 'end'
63
- ].join("\n"))
64
- end
50
+ include_examples 'reports offenses'
51
+
52
+ include_examples 'autocorrects offenses'
65
53
  end
66
54
  end
67
55
 
68
56
  context 'with header method `test_cask`' do
69
57
  context 'with no dsl version' do
70
- it 'passes' do
71
- inspect_source(cop, [
72
- "test_cask 'foo' do",
73
- 'end'
74
- ])
75
- expect(cop.offenses).to be_empty
76
- end
58
+ let(:source) { "test_cask 'foo' do; end" }
59
+
60
+ include_examples 'does not report any offenses'
77
61
  end
78
62
 
79
63
  context 'with dsl version' do
80
- it 'fails' do
81
- inspect_source(cop, [
82
- "test_cask :v1 => 'foo' do",
83
- 'end'
84
- ])
85
- expect(cop.offenses.size).to eq(1)
86
- expect(cop.offenses.first.line).to eq(1)
87
- expect(cop.messages)
88
- .to eq(["Use `test_cask 'foo'` instead of `test_cask :v1 => 'foo'`"])
89
- expect(cop.highlights)
90
- .to eq(["test_cask :v1 => 'foo'"])
64
+ let(:source) { "test_cask :v1 => 'foo' do; end" }
65
+ let(:correct_source) { "test_cask 'foo' do; end" }
66
+ let(:expected_offenses) do
67
+ [
68
+ {
69
+ message: "Use `test_cask 'foo'` instead of "\
70
+ "`test_cask :v1 => 'foo'`",
71
+ severity: :convention,
72
+ line: 1,
73
+ column: 0,
74
+ source: "test_cask :v1 => 'foo'"
75
+ }
76
+ ]
91
77
  end
92
78
 
93
- it 'autocorrects' do
94
- new_source = autocorrect_source(cop, [
95
- "test_cask :v1 => 'foo' do",
96
- 'end'
97
- ])
98
- expect(new_source).to eq([
99
- "test_cask 'foo' do",
100
- 'end'
101
- ].join("\n"))
102
- end
79
+ include_examples 'reports offenses'
80
+
81
+ include_examples 'autocorrects offenses'
103
82
  end
104
83
  end
105
84
  end
@@ -0,0 +1,194 @@
1
+ describe RuboCop::Cop::Cask::StanzaGrouping do
2
+ include CopSharedExamples
3
+
4
+ subject(:cop) { described_class.new }
5
+ let(:missing_line_msg) do
6
+ 'stanza groups should be separated by a single empty line'
7
+ end
8
+ let(:extra_line_msg) do
9
+ 'stanzas within the same group should have no lines between them'
10
+ end
11
+
12
+ context 'when no stanzas are incorrectly grouped' do
13
+ let(:source) do
14
+ <<-CASK.undent
15
+ cask 'foo' do
16
+ version :latest
17
+ sha256 :no_check
18
+ end
19
+ CASK
20
+ end
21
+
22
+ include_examples 'does not report any offenses'
23
+ end
24
+
25
+ context 'when one stanza is incorrectly grouped' do
26
+ let(:source) do
27
+ <<-CASK.undent
28
+ cask 'foo' do
29
+ version :latest
30
+
31
+ sha256 :no_check
32
+ end
33
+ CASK
34
+ end
35
+ let(:correct_source) do
36
+ <<-CASK.undent
37
+ cask 'foo' do
38
+ version :latest
39
+ sha256 :no_check
40
+ end
41
+ CASK
42
+ end
43
+ let(:expected_offenses) do
44
+ [
45
+ {
46
+ message: extra_line_msg,
47
+ severity: :convention,
48
+ line: 3,
49
+ column: 0,
50
+ source: "\n"
51
+ }
52
+ ]
53
+ end
54
+
55
+ include_examples 'reports offenses'
56
+
57
+ include_examples 'autocorrects offenses'
58
+ end
59
+
60
+ context 'when many stanzas are incorrectly grouped' do
61
+ let(:source) do
62
+ <<-CASK.undent
63
+ cask 'foo' do
64
+ version :latest
65
+ sha256 :no_check
66
+ url 'https://foo.example.com/foo.zip'
67
+
68
+ name 'Foo'
69
+
70
+ homepage 'https://foo.example.com'
71
+ license :mit
72
+
73
+ app 'Foo.app'
74
+ uninstall :quit => 'com.example.foo',
75
+ :kext => 'com.example.foo.kextextension'
76
+ end
77
+ CASK
78
+ end
79
+ let(:correct_source) do
80
+ <<-CASK.undent
81
+ cask 'foo' do
82
+ version :latest
83
+ sha256 :no_check
84
+
85
+ url 'https://foo.example.com/foo.zip'
86
+ name 'Foo'
87
+ homepage 'https://foo.example.com'
88
+ license :mit
89
+
90
+ app 'Foo.app'
91
+
92
+ uninstall :quit => 'com.example.foo',
93
+ :kext => 'com.example.foo.kextextension'
94
+ end
95
+ CASK
96
+ end
97
+ let(:expected_offenses) do
98
+ [
99
+ {
100
+ message: missing_line_msg,
101
+ severity: :convention,
102
+ line: 4,
103
+ column: 0,
104
+ source: " url 'https://foo.example.com/foo.zip'"
105
+ },
106
+ {
107
+ message: extra_line_msg,
108
+ severity: :convention,
109
+ line: 5,
110
+ column: 0,
111
+ source: "\n"
112
+ },
113
+ {
114
+ message: extra_line_msg,
115
+ severity: :convention,
116
+ line: 7,
117
+ column: 0,
118
+ source: "\n"
119
+ },
120
+ {
121
+ message: missing_line_msg,
122
+ severity: :convention,
123
+ line: 12,
124
+ column: 0,
125
+ source: " uninstall :quit => 'com.example.foo',"
126
+ }
127
+ ]
128
+ end
129
+
130
+ include_examples 'reports offenses'
131
+
132
+ include_examples 'autocorrects offenses'
133
+ end
134
+
135
+ context 'when a stanza includes a heredoc' do
136
+ let(:source) do
137
+ <<-CASK.undent
138
+ cask 'foo' do
139
+ version :latest
140
+ sha256 :no_check
141
+ url 'https://foo.example.com/foo.zip'
142
+ name 'Foo'
143
+ caveats <<-EOS.undent
144
+ This is a multiline caveat.
145
+
146
+ Let's hope it doesn't cause any problems!
147
+ EOS
148
+ app 'Foo.app'
149
+ end
150
+ CASK
151
+ end
152
+ let(:correct_source) do
153
+ <<-CASK.undent
154
+ cask 'foo' do
155
+ version :latest
156
+ sha256 :no_check
157
+
158
+ url 'https://foo.example.com/foo.zip'
159
+ name 'Foo'
160
+
161
+ caveats <<-EOS.undent
162
+ This is a multiline caveat.
163
+
164
+ Let's hope it doesn't cause any problems!
165
+ EOS
166
+
167
+ app 'Foo.app'
168
+ end
169
+ CASK
170
+ end
171
+
172
+ it 'autocorrects as expected' do
173
+ new_source = autocorrect_source(cop, source)
174
+ expect(new_source).to eq(Array(correct_source).join("\n"))
175
+ end
176
+ end
177
+
178
+ # TODO: detect incorrectly grouped stanzas in nested expressions
179
+ context 'when stanzas are nested in a conditional expression' do
180
+ let(:source) do
181
+ <<-CASK.undent
182
+ cask 'foo' do
183
+ if true
184
+ version :latest
185
+
186
+ sha256 :no_check
187
+ end
188
+ end
189
+ CASK
190
+ end
191
+
192
+ include_examples 'does not report any offenses'
193
+ end
194
+ end
@@ -0,0 +1,213 @@
1
+ describe RuboCop::Cop::Cask::StanzaOrder do
2
+ include CopSharedExamples
3
+
4
+ subject(:cop) { described_class.new }
5
+
6
+ context 'when no stanzas are out of order' do
7
+ let(:source) do
8
+ <<-CASK.undent
9
+ cask 'foo' do
10
+ version :latest
11
+ sha256 :no_check
12
+ end
13
+ CASK
14
+ end
15
+
16
+ include_examples 'does not report any offenses'
17
+ end
18
+
19
+ context 'when one pair of stanzas is out of order' do
20
+ let(:source) do
21
+ <<-CASK.undent
22
+ cask 'foo' do
23
+ sha256 :no_check
24
+ version :latest
25
+ end
26
+ CASK
27
+ end
28
+ let(:correct_source) do
29
+ <<-CASK.undent
30
+ cask 'foo' do
31
+ version :latest
32
+ sha256 :no_check
33
+ end
34
+ CASK
35
+ end
36
+ let(:expected_offenses) do
37
+ [
38
+ {
39
+ message: '`sha256` stanza out of order',
40
+ severity: :convention,
41
+ line: 2,
42
+ column: 2,
43
+ source: 'sha256 :no_check'
44
+ },
45
+ {
46
+ message: '`version` stanza out of order',
47
+ severity: :convention,
48
+ line: 3,
49
+ column: 2,
50
+ source: 'version :latest'
51
+ }
52
+ ]
53
+ end
54
+
55
+ include_examples 'reports offenses'
56
+
57
+ include_examples 'autocorrects offenses'
58
+ end
59
+
60
+ context 'when many stanzas are out of order' do
61
+ let(:source) do
62
+ <<-CASK.undent
63
+ cask 'foo' do
64
+ url 'https://foo.example.com/foo.zip'
65
+ uninstall :quit => 'com.example.foo',
66
+ :kext => 'com.example.foo.kext'
67
+ version :latest
68
+ app 'Foo.app'
69
+ sha256 :no_check
70
+ end
71
+ CASK
72
+ end
73
+ let(:correct_source) do
74
+ <<-CASK.undent
75
+ cask 'foo' do
76
+ version :latest
77
+ sha256 :no_check
78
+ url 'https://foo.example.com/foo.zip'
79
+ app 'Foo.app'
80
+ uninstall :quit => 'com.example.foo',
81
+ :kext => 'com.example.foo.kext'
82
+ end
83
+ CASK
84
+ end
85
+ let(:expected_offenses) do
86
+ [
87
+ {
88
+ message: '`url` stanza out of order',
89
+ severity: :convention,
90
+ line: 2,
91
+ column: 2,
92
+ source: "url 'https://foo.example.com/foo.zip'"
93
+ },
94
+ {
95
+ message: '`uninstall` stanza out of order',
96
+ severity: :convention,
97
+ line: 3,
98
+ column: 2,
99
+ source: "uninstall :quit => 'com.example.foo',\n" \
100
+ " :kext => 'com.example.foo.kext'"
101
+ },
102
+ {
103
+ message: '`version` stanza out of order',
104
+ severity: :convention,
105
+ line: 5,
106
+ column: 2,
107
+ source: 'version :latest'
108
+ },
109
+ {
110
+ message: '`sha256` stanza out of order',
111
+ severity: :convention,
112
+ line: 7,
113
+ column: 2,
114
+ source: 'sha256 :no_check'
115
+ }
116
+ ]
117
+ end
118
+
119
+ include_examples 'reports offenses'
120
+
121
+ include_examples 'autocorrects offenses'
122
+ end
123
+
124
+ context 'when a stanza appears multiple times' do
125
+ let(:source) do
126
+ <<-CASK.undent
127
+ cask 'foo' do
128
+ name 'Foo'
129
+ url 'https://foo.example.com/foo.zip'
130
+ name 'FancyFoo'
131
+ version :latest
132
+ app 'Foo.app'
133
+ sha256 :no_check
134
+ name 'FunkyFoo'
135
+ end
136
+ CASK
137
+ end
138
+ let(:correct_source) do
139
+ <<-CASK.undent
140
+ cask 'foo' do
141
+ version :latest
142
+ sha256 :no_check
143
+ url 'https://foo.example.com/foo.zip'
144
+ name 'Foo'
145
+ name 'FancyFoo'
146
+ name 'FunkyFoo'
147
+ app 'Foo.app'
148
+ end
149
+ CASK
150
+ end
151
+
152
+ it 'preserves the original order' do
153
+ new_source = autocorrect_source(cop, source)
154
+ expect(new_source).to eq(Array(correct_source).join("\n"))
155
+ end
156
+ end
157
+
158
+ context 'when a stanza includes a heredoc' do
159
+ let(:source) do
160
+ <<-CASK.undent
161
+ cask 'foo' do
162
+ name 'Foo'
163
+ url 'https://foo.example.com/foo.zip'
164
+ caveats <<-EOS.undent
165
+ This is a multiline caveat.
166
+
167
+ Let's hope it doesn't cause any problems!
168
+ EOS
169
+ version :latest
170
+ app 'Foo.app'
171
+ sha256 :no_check
172
+ end
173
+ CASK
174
+ end
175
+ let(:correct_source) do
176
+ <<-CASK.undent
177
+ cask 'foo' do
178
+ version :latest
179
+ sha256 :no_check
180
+ url 'https://foo.example.com/foo.zip'
181
+ name 'Foo'
182
+ app 'Foo.app'
183
+ caveats <<-EOS.undent
184
+ This is a multiline caveat.
185
+
186
+ Let's hope it doesn't cause any problems!
187
+ EOS
188
+ end
189
+ CASK
190
+ end
191
+
192
+ it 'autocorrects as expected' do
193
+ new_source = autocorrect_source(cop, source)
194
+ expect(new_source).to eq(Array(correct_source).join("\n"))
195
+ end
196
+ end
197
+
198
+ # TODO: detect out-of-order stanzas in nested expressions
199
+ context 'when stanzas are nested in a conditional expression' do
200
+ let(:source) do
201
+ <<-CASK.undent
202
+ cask 'foo' do
203
+ if true
204
+ sha256 :no_check
205
+ version :latest
206
+ end
207
+ end
208
+ CASK
209
+ end
210
+
211
+ include_examples 'does not report any offenses'
212
+ end
213
+ end