rubocop-discourse 3.8.6 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf5c1b4b83dac7f5180e776e8161b812987f18e95b16d0eaca23d24256be6ace
4
- data.tar.gz: e15870cbf907b1561cbf8f669e2dbda6043e664c271ddaca8649d8183463c271
3
+ metadata.gz: 416bda6686bc9313c67819f5cdc6e47dd25b68cae808fcb190c1da14b1fda371
4
+ data.tar.gz: 577720bc7b63aa1fa574ea6da53c9326010e35ce2dc9e367eb888e459d9d17d3
5
5
  SHA512:
6
- metadata.gz: efcbbb0a6935fd5f59ae3a1c40abd05af96618cc8eb172fdba9e2e073e2c0cdb0119a280244fe7b89d0c62ca418f3bc14e51b486411978e240d6edfe77e22cbf
7
- data.tar.gz: 6cdbe8ffdd66166f2a617bf654579a284db4342f9fa5f30186a760a51bb73e6e173b2b09c87d8282905440c6d79999cc5f87254cc3f6ee7e2fb596d4240da1a9
6
+ metadata.gz: 569fb633072ebff20f3187fd684073d0d1d4ebbaf090cfc173de1c9c794933276ef09beb105d5af2bc639c8b72aa9e221bbcef9d5041d5ab3300dd4a82b64c52
7
+ data.tar.gz: 6eb8ced2313523c8c6fa0c1681bab7103a9b5b21d97dba683a100bf81bbe082515468d7468c633d46158348d0c92762a32aada56c72afc126b4e28bde5f0b0d8
data/config/default.yml CHANGED
@@ -90,3 +90,9 @@ Discourse/Plugins/UseRequireRelative:
90
90
 
91
91
  Discourse/Plugins/NoMonkeyPatching:
92
92
  Enabled: true
93
+
94
+ Discourse/Services/GroupKeywords:
95
+ Enabled: true
96
+
97
+ Discourse/Services/EmptyLinesAroundBlocks:
98
+ Enabled: true
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Discourse
6
+ module Services
7
+ # Put empty lines around multiline blocks.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # model :my_model
12
+ # params do
13
+ # attribute :my_attribute
14
+ # validates :my_attribute, presence: true
15
+ # end
16
+ # policy :my_policy
17
+ # step :another_step
18
+ #
19
+ # # good
20
+ # model :my_model
21
+ #
22
+ # params do
23
+ # attribute :my_attribute
24
+ # validates :my_attribute, presence: true
25
+ # end
26
+ #
27
+ # policy :my_policy
28
+ # step :another_step
29
+ #
30
+ class EmptyLinesAroundBlocks < Base
31
+ extend AutoCorrector
32
+
33
+ MSG = "Add empty lines around a step block."
34
+
35
+ def_node_matcher :service_include?, <<~MATCHER
36
+ (class _ _
37
+ {
38
+ (begin <(send nil? :include (const (const nil? :Service) :Base)) ...>)
39
+ <(send nil? :include (const (const nil? :Service) :Base)) ...>
40
+ }
41
+ )
42
+ MATCHER
43
+
44
+ def_node_matcher :top_level_block?, <<~MATCHER
45
+ (block (send nil? _) ...)
46
+ MATCHER
47
+
48
+ def on_class(node)
49
+ return unless service_include?(node)
50
+ @service = true
51
+ end
52
+
53
+ def on_block(node)
54
+ return unless service?
55
+ return unless top_level_block?(node)
56
+ return if node.single_line?
57
+
58
+ if missing_empty_lines?(node)
59
+ add_offense(node, message: MSG) do |corrector|
60
+ if missing_empty_line_before?(node) &&
61
+ corrected_after.exclude?(node.left_sibling)
62
+ corrected_before << node
63
+ corrector.insert_before(
64
+ node.loc.expression.adjust(
65
+ begin_pos: -node.loc.expression.column
66
+ ),
67
+ "\n"
68
+ )
69
+ end
70
+ if missing_empty_line_after?(node) &&
71
+ corrected_before.exclude?(node.right_sibling)
72
+ corrected_after << node
73
+ corrector.insert_after(node.loc.end, "\n")
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def service?
82
+ @service
83
+ end
84
+
85
+ def missing_empty_lines?(node)
86
+ missing_empty_line_before?(node) || missing_empty_line_after?(node)
87
+ end
88
+
89
+ def missing_empty_line_before?(node)
90
+ processed_source[node.loc.expression.line - 2].present? &&
91
+ node.left_siblings.present?
92
+ end
93
+
94
+ def missing_empty_line_after?(node)
95
+ processed_source[node.loc.end.line].present? &&
96
+ node.right_siblings.present?
97
+ end
98
+
99
+ def corrected_before
100
+ @corrected_before ||= []
101
+ end
102
+
103
+ def corrected_after
104
+ @corrected_before ||= []
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Discourse
6
+ module Services
7
+ # Don’t put empty lines between keywords that are not multiline blocks.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # model :my_model
12
+ #
13
+ # policy :my_policy
14
+ #
15
+ # try { step :might_raise }
16
+ #
17
+ # # good
18
+ # model :my_model
19
+ # policy :my_policy
20
+ # try { step :might_raise }
21
+ #
22
+ class GroupKeywords < Base
23
+ extend AutoCorrector
24
+
25
+ MSG = "Group one-liner steps together by removing extra empty lines."
26
+ RESTRICT_ON_SEND = %i[step model policy].freeze
27
+
28
+ def_node_matcher :service_include?, <<~MATCHER
29
+ (class _ _
30
+ {
31
+ (begin <(send nil? :include (const (const nil? :Service) :Base)) ...>)
32
+ <(send nil? :include (const (const nil? :Service) :Base)) ...>
33
+ }
34
+ )
35
+ MATCHER
36
+
37
+ def on_class(node)
38
+ return unless service_include?(node)
39
+ @service = true
40
+ end
41
+
42
+ def on_send(node)
43
+ return unless service?
44
+ return unless top_level?(node)
45
+ return unless extra_empty_line_after?(node)
46
+
47
+ add_offense(node, message: MSG) do |corrector|
48
+ range =
49
+ node.loc.expression.end.with(
50
+ end_pos: node.right_sibling.loc.expression.begin_pos
51
+ )
52
+ content = range.source.gsub(/^(\n)+/, "\n")
53
+ corrector.replace(range, content)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def service?
60
+ @service
61
+ end
62
+
63
+ def extra_empty_line_after?(node)
64
+ processed_source[node.loc.expression.line].blank? &&
65
+ (
66
+ service_keyword?(node.right_sibling) ||
67
+ single_line_block?(node.right_sibling)
68
+ )
69
+ end
70
+
71
+ def service_keyword?(node)
72
+ return unless node
73
+ node.send_type? && RESTRICT_ON_SEND.include?(node.method_name)
74
+ end
75
+
76
+ def single_line_block?(node)
77
+ return unless node
78
+ node.block_type? && node.single_line?
79
+ end
80
+
81
+ def top_level?(node)
82
+ while (!node.root?)
83
+ node = node.parent
84
+ return if %i[begin class block].exclude?(node.type)
85
+ end
86
+ true
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -3,6 +3,8 @@
3
3
  require "rubocop"
4
4
  require "active_support"
5
5
  require "active_support/core_ext/string/inflections"
6
+ require "active_support/core_ext/object/blank"
7
+ require "active_support/core_ext/enumerable"
6
8
  require_relative "rubocop/discourse"
7
9
  require_relative "rubocop/discourse/inject"
8
10
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "rubocop-discourse"
5
- s.version = "3.8.6"
5
+ s.version = "3.9.0"
6
6
  s.summary = "Custom rubocop cops used by Discourse"
7
7
  s.authors = ["Discourse Team"]
8
8
  s.license = "MIT"
@@ -0,0 +1,294 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe RuboCop::Cop::Discourse::Services::EmptyLinesAroundBlocks,
6
+ :config do
7
+ subject(:cop) { described_class.new(config) }
8
+
9
+ let(:config) { RuboCop::Config.new }
10
+
11
+ context "when not in a service class" do
12
+ it "does nothing" do
13
+ expect_no_offenses(<<~RUBY)
14
+ class NotAService
15
+ step :first_step
16
+ params do
17
+ attribute :my_attribute
18
+ end
19
+ step :another_step
20
+ end
21
+ RUBY
22
+ end
23
+ end
24
+
25
+ context "when in a service class" do
26
+ context "when a blank line is missing before a block" do
27
+ it "registers an offense" do
28
+ expect_offense(<<~RUBY)
29
+ class MyService
30
+ include Service::Base
31
+
32
+ step :first_step
33
+ params do
34
+ ^^^^^^^^^ Discourse/Services/EmptyLinesAroundBlocks: Add empty lines around a step block.
35
+ attribute :my_attribute
36
+ end
37
+ end
38
+ RUBY
39
+
40
+ expect_correction(<<~RUBY)
41
+ class MyService
42
+ include Service::Base
43
+
44
+ step :first_step
45
+
46
+ params do
47
+ attribute :my_attribute
48
+ end
49
+ end
50
+ RUBY
51
+ end
52
+ end
53
+
54
+ context "when a blank line is missing after a block" do
55
+ it "registers an offense" do
56
+ expect_offense(<<~RUBY)
57
+ class MyService
58
+ include Service::Base
59
+
60
+ params do
61
+ ^^^^^^^^^ Discourse/Services/EmptyLinesAroundBlocks: Add empty lines around a step block.
62
+ attribute :my_attribute
63
+ end
64
+ step :last_step
65
+ end
66
+ RUBY
67
+
68
+ expect_correction(<<~RUBY)
69
+ class MyService
70
+ include Service::Base
71
+
72
+ params do
73
+ attribute :my_attribute
74
+ end
75
+
76
+ step :last_step
77
+ end
78
+ RUBY
79
+ end
80
+ end
81
+
82
+ context "when two blocks are next to each other" do
83
+ it "registers an offense" do
84
+ expect_offense(<<~RUBY)
85
+ class MyService
86
+ include Service::Base
87
+
88
+ params do
89
+ ^^^^^^^^^ Discourse/Services/EmptyLinesAroundBlocks: Add empty lines around a step block.
90
+ attribute :attribute
91
+ validates :attribute, presence: true
92
+ end
93
+ transaction do
94
+ ^^^^^^^^^^^^^^ Discourse/Services/EmptyLinesAroundBlocks: Add empty lines around a step block.
95
+ step :first
96
+ step :second
97
+ end
98
+ end
99
+ RUBY
100
+
101
+ expect_correction(<<~RUBY)
102
+ class MyService
103
+ include Service::Base
104
+
105
+ params do
106
+ attribute :attribute
107
+ validates :attribute, presence: true
108
+ end
109
+
110
+ transaction do
111
+ step :first
112
+ step :second
113
+ end
114
+ end
115
+ RUBY
116
+ end
117
+ end
118
+
119
+ context "when a block is a one-liner" do
120
+ it "does not register an offense" do
121
+ expect_no_offenses(<<~RUBY)
122
+ class MyService
123
+ include Service::Base
124
+
125
+ try { step :might_raise }
126
+ step :last_step
127
+ end
128
+ RUBY
129
+ end
130
+ end
131
+
132
+ context "when blocks are nested" do
133
+ context "when the nested block is in the first position" do
134
+ context "when there is no empty line before" do
135
+ it "does not register an offense" do
136
+ expect_no_offenses(<<~RUBY)
137
+ class MyService
138
+ include Service::Base
139
+
140
+ transaction do
141
+ try do
142
+ step :first_step
143
+ step :second_step
144
+ end
145
+
146
+ step :third_step
147
+ end
148
+ end
149
+ RUBY
150
+ end
151
+ end
152
+
153
+ context "when there is no empty line after" do
154
+ it "registers an offense" do
155
+ expect_offense(<<~RUBY)
156
+ class MyService
157
+ include Service::Base
158
+
159
+ transaction do
160
+ try do
161
+ ^^^^^^ Discourse/Services/EmptyLinesAroundBlocks: Add empty lines around a step block.
162
+ step :first_step
163
+ step :second_step
164
+ end
165
+ step :third_step
166
+ end
167
+ end
168
+ RUBY
169
+
170
+ expect_correction(<<~RUBY)
171
+ class MyService
172
+ include Service::Base
173
+
174
+ transaction do
175
+ try do
176
+ step :first_step
177
+ step :second_step
178
+ end
179
+
180
+ step :third_step
181
+ end
182
+ end
183
+ RUBY
184
+ end
185
+ end
186
+ end
187
+
188
+ context "when the nested block is in the last position" do
189
+ context "when there is no empty line after" do
190
+ it "does not register an offense" do
191
+ expect_no_offenses(<<~RUBY)
192
+ class MyService
193
+ include Service::Base
194
+
195
+ transaction do
196
+ step :first_step
197
+
198
+ try do
199
+ step :second_step
200
+ step :third_step
201
+ end
202
+ end
203
+ end
204
+ RUBY
205
+ end
206
+ end
207
+
208
+ context "when there is no empty line before" do
209
+ it "registers an offense" do
210
+ expect_offense(<<~RUBY)
211
+ class MyService
212
+ include Service::Base
213
+
214
+ transaction do
215
+ step :first_step
216
+ try do
217
+ ^^^^^^ Discourse/Services/EmptyLinesAroundBlocks: Add empty lines around a step block.
218
+ step :second_step
219
+ step :third_step
220
+ end
221
+ end
222
+ end
223
+ RUBY
224
+
225
+ expect_correction(<<~RUBY)
226
+ class MyService
227
+ include Service::Base
228
+
229
+ transaction do
230
+ step :first_step
231
+
232
+ try do
233
+ step :second_step
234
+ step :third_step
235
+ end
236
+ end
237
+ end
238
+ RUBY
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ context "when blocks are used in methods" do
245
+ it "does not register an offense" do
246
+ expect_no_offenses(<<~RUBY)
247
+ class MyService
248
+ include Service::Base
249
+
250
+ step :first_step
251
+
252
+ def first_step(model:)
253
+ model.transaction do
254
+ do_something
255
+ end
256
+ end
257
+ end
258
+ RUBY
259
+ end
260
+ end
261
+
262
+ context "with a full valid example" do
263
+ it "does not register an offense" do
264
+ expect_no_offenses(<<~RUBY)
265
+ class MyService
266
+ include Service::Base
267
+
268
+ step :first_step
269
+
270
+ params do
271
+ attribute :my_attribute
272
+
273
+ validates :my_attributes, presence: true
274
+ end
275
+
276
+ policy :allowed?
277
+ model :user
278
+
279
+ transaction do
280
+ try do
281
+ step :save_user
282
+ step :log
283
+ end
284
+
285
+ step :other_step
286
+ end
287
+
288
+ step :last_step
289
+ end
290
+ RUBY
291
+ end
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe RuboCop::Cop::Discourse::Services::GroupKeywords, :config do
6
+ subject(:cop) { described_class.new(config) }
7
+
8
+ let(:config) { RuboCop::Config.new }
9
+
10
+ context "when not in a service class" do
11
+ it "does nothing" do
12
+ expect_no_offenses(<<~RUBY)
13
+ class NotAService
14
+ step :first_step
15
+
16
+ step :another_step
17
+ end
18
+ RUBY
19
+ end
20
+ end
21
+
22
+ context "when in a service class" do
23
+ context "when keywords are not grouped together" do
24
+ it "reports an offense" do
25
+ expect_offense(<<~RUBY)
26
+ class MyService
27
+ include Service::Base
28
+
29
+ model :user
30
+ ^^^^^^^^^^^ Discourse/Services/GroupKeywords: Group one-liner steps together by removing extra empty lines.
31
+
32
+ policy :allowed?
33
+ step :save
34
+ end
35
+ RUBY
36
+
37
+ expect_correction(<<~RUBY)
38
+ class MyService
39
+ include Service::Base
40
+
41
+ model :user
42
+ policy :allowed?
43
+ step :save
44
+ end
45
+ RUBY
46
+ end
47
+ end
48
+
49
+ context "when a one-liner block has an empty line before a keyword" do
50
+ it "reports an offense" do
51
+ expect_offense(<<~RUBY)
52
+ class MyService
53
+ include Service::Base
54
+
55
+ model :user
56
+ policy :allowed?
57
+ ^^^^^^^^^^^^^^^^ Discourse/Services/GroupKeywords: Group one-liner steps together by removing extra empty lines.
58
+
59
+ try { step :save }
60
+ end
61
+ RUBY
62
+
63
+ expect_correction(<<~RUBY)
64
+ class MyService
65
+ include Service::Base
66
+
67
+ model :user
68
+ policy :allowed?
69
+ try { step :save }
70
+ end
71
+ RUBY
72
+ end
73
+ end
74
+
75
+ context "when keywords with empty lines appear in a nested block" do
76
+ it "reports an offense" do
77
+ expect_offense(<<~RUBY)
78
+ class MyService
79
+ include Service::Base
80
+
81
+ transaction do
82
+ step :save
83
+ ^^^^^^^^^^ Discourse/Services/GroupKeywords: Group one-liner steps together by removing extra empty lines.
84
+
85
+ step :log
86
+ end
87
+ end
88
+ RUBY
89
+
90
+ expect_correction(<<~RUBY)
91
+ class MyService
92
+ include Service::Base
93
+
94
+ transaction do
95
+ step :save
96
+ step :log
97
+ end
98
+ end
99
+ RUBY
100
+ end
101
+ end
102
+
103
+ context "when keywords are grouped together" do
104
+ it "does not report an offense" do
105
+ expect_no_offenses(<<~RUBY)
106
+ class MyService
107
+ include Service::Base
108
+
109
+ model :user
110
+ policy :allowed?
111
+
112
+ transaction do
113
+ step :save
114
+ step :log
115
+ end
116
+ end
117
+ RUBY
118
+ end
119
+ end
120
+
121
+ context "when keywords are not at the top level" do
122
+ it "does not report an offense" do
123
+ expect_no_offenses(<<~RUBY)
124
+ class MyService
125
+ include Service::Base
126
+
127
+ private
128
+
129
+ def my_method
130
+ step(:save)
131
+
132
+ step(:log)
133
+ end
134
+ end
135
+ RUBY
136
+ end
137
+ end
138
+ end
139
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-discourse
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.8.6
4
+ version: 3.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Discourse Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-13 00:00:00.000000000 Z
11
+ date: 2024-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -170,6 +170,8 @@ files:
170
170
  - lib/rubocop/cop/discourse/plugins/no_monkey_patching.rb
171
171
  - lib/rubocop/cop/discourse/plugins/use_plugin_instance_on.rb
172
172
  - lib/rubocop/cop/discourse/plugins/use_require_relative.rb
173
+ - lib/rubocop/cop/discourse/services/empty_lines_around_blocks.rb
174
+ - lib/rubocop/cop/discourse/services/group_keywords.rb
173
175
  - lib/rubocop/cop/discourse/time_eq_matcher.rb
174
176
  - lib/rubocop/cop/discourse_cops.rb
175
177
  - lib/rubocop/discourse.rb
@@ -188,6 +190,8 @@ files:
188
190
  - spec/fixtures/controllers/namespaced_parent_controller.rb
189
191
  - spec/fixtures/controllers/no_requires_plugin_controller.rb
190
192
  - spec/fixtures/controllers/requires_plugin_controller.rb
193
+ - spec/lib/rubocop/cop/discourse/services/empty_lines_around_blocks_spec.rb
194
+ - spec/lib/rubocop/cop/discourse/services/group_keywords_spec.rb
191
195
  - spec/lib/rubocop/cop/fabricator_shorthand_spec.rb
192
196
  - spec/lib/rubocop/cop/no_add_reference_active_record_migrations_spec.rb
193
197
  - spec/lib/rubocop/cop/no_mixing_multisite_and_standard_specs_spec.rb