rubocop-discourse 3.8.6 → 3.9.0

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 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