gl_rubocop 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2dc8147315555f5d211c9d5577a449f69d73b913126481bc1477ea5ede612f38
4
- data.tar.gz: ddd3961ae821ea369248be94d91dc23559ef5993261f75e660cdf05246a28358
3
+ metadata.gz: 55c5e4d549add201fba2d9cdcf5c63b3adf2fff339785be177d2b992c841750a
4
+ data.tar.gz: 50d1687524ca84b65fca2ab5e8d9502446991aa69b3d0b06f9711d614393785d
5
5
  SHA512:
6
- metadata.gz: d1b05b7ffd3b6917ac6838fea98e817572629d5b25258816b773a69671b51487754caabf5283cb840451b800f2f695fa551b4ddf4faebfe93ab2c914a7456f70
7
- data.tar.gz: 60be23fbb3bfb84505da907c041bb7708a1f0b7f5e7ff5dcfbd6b3fa1db37866880092612d7a7302ec5eefdf6f97751244bf0df37e5ecd95223b1ce74a0e2dee
6
+ metadata.gz: 3ddaebd810428c15841487137a7f692a17124bd95278244422107540690858398c69667111102f9f9d43e18a5e6326eef3e95fe6e77ff5341fbcdb6844b64aef
7
+ data.tar.gz: dd7ad7ea6d9f11648bf103da2e743540c6709444e28f926eb6d4692d768a4ae5a6c3da37f023b5ab9f08305fb7c6c52e91a43a57ec89e69555bf716564fa5432
data/default.yml CHANGED
@@ -9,16 +9,21 @@ require:
9
9
  - rubocop-rake
10
10
  - rubocop-sorbet
11
11
  - ./lib/gl_rubocop/gl_cops/callback_method_names.rb
12
+ - ./lib/gl_rubocop/gl_cops/consolidate_request_system_specs.rb
12
13
  - ./lib/gl_rubocop/gl_cops/interactor_inherits_from_interactor_base.rb
13
14
  - ./lib/gl_rubocop/gl_cops/limit_flash_options.rb
14
15
  - ./lib/gl_rubocop/gl_cops/no_stubbing_perform_async.rb
15
16
  - ./lib/gl_rubocop/gl_cops/prevent_haml_files.rb
16
17
  - ./lib/gl_rubocop/gl_cops/rails_cache.rb
17
18
  - ./lib/gl_rubocop/gl_cops/sidekiq_inherits_from_sidekiq_job.rb
18
- - ./lib/gl_rubocop/gl_cops/unique_identifier.rb
19
19
  - ./lib/gl_rubocop/gl_cops/tailwind_no_contradicting_class_name.rb
20
+ - ./lib/gl_rubocop/gl_cops/unique_identifier.rb
20
21
  - ./lib/gl_rubocop/gl_cops/valid_data_test_id.rb
22
+ - ./lib/gl_rubocop/gl_cops/vcr_cassette_names.rb
21
23
  - ./lib/gl_rubocop/gl_cops/view_component_initialize_keyword_args.rb
24
+ - ./lib/gl_rubocop/gl_cops/view_component_class_naming.rb
25
+ - ./lib/gl_rubocop/gl_cops/view_component_inheritance.rb
26
+ - ./lib/gl_rubocop/gl_cops/view_component_directory_structure.rb
22
27
 
23
28
  AllCops:
24
29
  SuggestExtensions: false
@@ -44,6 +49,11 @@ Capybara/NegationMatcher:
44
49
  GLCops/CallbackMethodNames:
45
50
  Enabled: true
46
51
 
52
+ GLCops/ConsolidateRequestSystemSpecs:
53
+ Enabled: true
54
+ Include:
55
+ - "**/*spec.rb"
56
+
47
57
  GLCops/InteractorInheritsFromInteractorBase:
48
58
  Enabled: true
49
59
  Include:
@@ -84,6 +94,26 @@ GLCops/ValidDataTestId:
84
94
  - "**/*.haml"
85
95
  - "**/*.rb"
86
96
 
97
+ GLCops/VcrCassetteNames:
98
+ Enabled: true
99
+ Include:
100
+ - "**/*spec.rb"
101
+
102
+ GLCops/ViewComponentClassNaming:
103
+ Enabled: true
104
+ Include:
105
+ - "app/components/**/*.rb"
106
+
107
+ GLCops/ViewComponentDirectoryStructure:
108
+ Enabled: true
109
+ Include:
110
+ - "app/components/**/component.rb"
111
+
112
+ GLCops/ViewComponentInheritance:
113
+ Enabled: true
114
+ Include:
115
+ - "app/components/**/component.rb"
116
+
87
117
  GLCops/ViewComponentInitializeKeywordArgs:
88
118
  Enabled: true
89
119
  Include:
@@ -1,4 +1,3 @@
1
- # rubocop:disable I18n/RailsI18n/DecorateString
2
1
  module GLRubocop
3
2
  module GLCops
4
3
  # This cop ensures that controller callbacks are named methods, not inline blocks.
@@ -22,4 +21,3 @@ module GLRubocop
22
21
  end
23
22
  end
24
23
  end
25
- # rubocop:enable I18n/RailsI18n/DecorateString
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GLRubocop
4
+ module GLCops
5
+ # This cop ensures request and system specs consolidate examples in a single it block,
6
+ # per each describe and context.
7
+ # Reason: Setup for specs should go in let/before blocks - which are different for each context.
8
+ # it blocks with the same setup should be consolidated to keep our tests fast.
9
+ #
10
+ #
11
+ # Good:
12
+ # RSpec.describe UsersController, type: :request do
13
+ # describe 'GET /users' do
14
+ # it 'returns users' do
15
+ # get users_path
16
+ # expect(response).to be_successful
17
+ # expect(response).to render_template(:index)
18
+ #
19
+ # get users_path, headers: {format: :json}
20
+ # expect(response).to be_successful
21
+ # expect(response.content_type).to eq('application/json')
22
+ # end
23
+ # end
24
+ # describe 'GET /users/id' do
25
+ # let(:user_id) { user.id }
26
+ # it 'returns user' do
27
+ # get user_path(user_id)
28
+ # expect(response).to be_successful
29
+ # end
30
+ # context 'with unknown user' do
31
+ # let(:user_id) { 111111 }
32
+ # it 'does not return user' do
33
+ # get user_path(user_id)
34
+ # expect(response).not_to be_successful
35
+ # end
36
+ # end
37
+ # end
38
+ # end
39
+ #
40
+ # Bad:
41
+ # RSpec.describe UsersController, type: :request do
42
+ # describe 'GET /users' do
43
+ # it 'returns success' do
44
+ # get users_path
45
+ # expect(response).to be_successful
46
+ # end
47
+ #
48
+ # it 'returns json' do
49
+ # get users_path
50
+ # expect(response.content_type).to eq('application/json')
51
+ # end
52
+ # end
53
+ # end
54
+ class ConsolidateRequestSystemSpecs < RuboCop::Cop::Base
55
+ MSG = 'Consolidate examples with the same setup in request specs and system specs. ' \
56
+ 'Use a single it block instead of multiple it blocks.'
57
+
58
+ RSPEC_EXAMPLE_METHODS = %i[it specify example].freeze
59
+
60
+ # @!method rspec_group?(node)
61
+ def_node_matcher :rspec_group?, <<~PATTERN
62
+ (block (send _ {:describe :context} ...) ...)
63
+ PATTERN
64
+
65
+ # @!method request_or_system_type?(node)
66
+ def_node_matcher :request_or_system_type?, <<~PATTERN
67
+ (block (send _ _ ... (hash <(pair (sym :type) (sym {:request :system})) ...>)) ...)
68
+ PATTERN
69
+
70
+ def on_new_investigation
71
+ @spec_type_cache = {}
72
+ end
73
+
74
+ def on_block(node)
75
+ return unless rspec_group?(node)
76
+ return unless request_or_system_spec?(node)
77
+
78
+ check_multiple_examples(node)
79
+ end
80
+
81
+ private
82
+
83
+ def request_or_system_spec?(node)
84
+ current = node
85
+ while current
86
+ if current.block_type?
87
+ unless @spec_type_cache.key?(current)
88
+ @spec_type_cache[current] =
89
+ request_or_system_type?(current)
90
+ end
91
+ return true if @spec_type_cache[current]
92
+ end
93
+ current = current.parent
94
+ end
95
+
96
+ false
97
+ end
98
+
99
+ def check_multiple_examples(node)
100
+ examples = find_example_blocks(node)
101
+ return if examples.size <= 1
102
+
103
+ examples[1..].each do |example_node|
104
+ add_offense(example_node, message: MSG)
105
+ end
106
+ end
107
+
108
+ def find_example_blocks(node)
109
+ return [] unless node.body
110
+
111
+ node.body.each_child_node(:block).select do |child|
112
+ RSPEC_EXAMPLE_METHODS.include?(child.send_node.method_name)
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GLRubocop
4
+ module GLCops
5
+ # This cop ensures that VCR cassettes have names.
6
+ #
7
+ # Good:
8
+ # VCR.use_cassette('cassette_name') { ... }
9
+ # VCR.use_cassette("cassette_name") { ... }
10
+ # describe '.create', vcr: { cassette_name: :chariot_connect_create } do
11
+ #
12
+ # Bad:
13
+ # VCR.use_cassette { ... }
14
+ # VCR.use_cassette() { ... }
15
+ # describe 'something', :vcr do
16
+ class VcrCassetteNames < RuboCop::Cop::Base
17
+ MSG = 'VCR cassettes must have a name. Example: VCR.use_cassette("cassette_name") { ... }'
18
+ RSPEC_MSG = 'VCR cassettes must have a name. ' \
19
+ 'Example: describe "test", vcr: { cassette_name: :my_cassette } do'
20
+
21
+ RSPEC_METHODS = %i[describe context it specify example].freeze
22
+
23
+ # @!method vcr_use_cassette?(node)
24
+ def_node_matcher :vcr_use_cassette?, <<~PATTERN
25
+ (send (const nil? :VCR) :use_cassette ...)
26
+ PATTERN
27
+
28
+ # @!method vcr_use_cassette_with_name?(node)
29
+ def_node_matcher :vcr_use_cassette_with_name?, <<~PATTERN
30
+ (send (const nil? :VCR) :use_cassette {str dstr} ...)
31
+ PATTERN
32
+
33
+ # @!method rspec_vcr_symbol?(node)
34
+ def_node_matcher :rspec_vcr_symbol?, <<~PATTERN
35
+ (sym :vcr)
36
+ PATTERN
37
+
38
+ # @!method vcr_hash_without_cassette_name?(node)
39
+ def_node_matcher :vcr_hash_without_cassette_name?, <<~PATTERN
40
+ (pair (sym :vcr) !{(hash <(pair (sym :cassette_name) _) ...>)})
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ check_vcr_use_cassette(node)
45
+ check_rspec_metadata(node)
46
+ end
47
+
48
+ private
49
+
50
+ def check_vcr_use_cassette(node)
51
+ return unless vcr_use_cassette?(node)
52
+ return if vcr_use_cassette_with_name?(node)
53
+
54
+ add_offense(node, message: MSG)
55
+ end
56
+
57
+ def check_rspec_metadata(node)
58
+ return unless RSPEC_METHODS.include?(node.method_name)
59
+
60
+ node.arguments.each do |arg|
61
+ if rspec_vcr_symbol?(arg)
62
+ add_offense(arg, message: RSPEC_MSG)
63
+ elsif arg.hash_type?
64
+ arg.pairs.each do |pair|
65
+ add_offense(pair, message: RSPEC_MSG) if vcr_hash_without_cassette_name?(pair)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ module GLRubocop
2
+ module GLCops
3
+ # This cop checks that the class name is "Component" or "ApplicationViewComponent".
4
+ #
5
+ # Good:
6
+ # class Component < ViewComponent::Base
7
+ # end
8
+ #
9
+ # class ApplicationViewComponent < ViewComponent::Base
10
+ # end
11
+ #
12
+ # Bad:
13
+ # class UserCardComponent < ViewComponent::Base
14
+ # end
15
+ class ViewComponentClassNaming < RuboCop::Cop::Base
16
+ def on_class(node)
17
+ class_name = node.identifier.const_name
18
+ return true if class_name == 'Component'
19
+ return true if class_name == 'ApplicationViewComponent'
20
+
21
+ add_offense(node, message: 'ViewComponent class names must be "Component".')
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ module GLRubocop
2
+ module GLCops
3
+ # This cop checks that all ViewComponent is part of an allowlisted base module.
4
+ #
5
+ # Good:
6
+ # module Core
7
+ # module Users
8
+ # class Component < ApplicationViewComponent
9
+ # end
10
+ # end
11
+ # end
12
+ #
13
+ # Bad:
14
+ # module Billing
15
+ # class Component < ApplicationViewComponent
16
+ # end
17
+ # end
18
+ class ViewComponentDirectoryStructure < RuboCop::Cop::Base
19
+ MSG = 'ViewComponent must belong to an allowed base module: %<allowed>s'.freeze
20
+ ALLOWED_MODULES = %w[Core Admin NonprofitAdmin Packs Users].freeze
21
+
22
+ def on_class(node)
23
+ return true if node.identifier.const_name == 'ApplicationViewComponent'
24
+
25
+ base_module = node.parent_module_name&.split('::')&.first
26
+ return true if base_module.nil?
27
+ return true if ALLOWED_MODULES.include?(base_module)
28
+
29
+ add_offense(node, message: format(MSG, allowed: ALLOWED_MODULES.join(', ')))
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ module GLRubocop
2
+ module GLCops
3
+ # This cop checks that all ViewComponent classes inherit from an allowlisted base class.
4
+ #
5
+ # Good:
6
+ # class Components::HeroComponent < ApplicationViewComponent
7
+ # end
8
+ #
9
+ # class Components::CardComponent < ViewComponent::Base
10
+ # end
11
+ #
12
+ # Bad:
13
+ # class Components::HeroComponent < ViewComponent
14
+ # end
15
+ #
16
+ # class Components::CardComponent
17
+ # end
18
+ class ViewComponentInheritance < RuboCop::Cop::Base
19
+ INHERITANCE_MSG = 'ViewComponent must inherit from ApplicationViewComponent'.freeze
20
+
21
+ def on_class(node)
22
+ return true if inherits_from_application_view_component(node)
23
+
24
+ add_offense(node, message: INHERITANCE_MSG)
25
+ end
26
+
27
+ private
28
+
29
+ def inherits_from_application_view_component(node)
30
+ parent = node.parent_class
31
+ return false unless parent
32
+
33
+ parent_name = parent.const_name
34
+ %w[ApplicationViewComponent ViewComponent::Base].include?(parent_name)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,4 +1,3 @@
1
- # rubocop:disable I18n/RailsI18n/DecorateString
2
1
  module GLRubocop
3
2
  module GLCops
4
3
  # This cop ensures that ViewComponent initialize methods use keyword arguments only.
@@ -27,4 +26,3 @@ module GLRubocop
27
26
  end
28
27
  end
29
28
  end
30
- # rubocop:enable I18n/RailsI18n/DecorateString
@@ -1,3 +1,3 @@
1
1
  module GLRubocop
2
- VERSION = '0.4.0'.freeze
2
+ VERSION = '0.5.1'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gl_rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Give Lively
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-10 00:00:00.000000000 Z
11
+ date: 2026-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -163,6 +163,7 @@ files:
163
163
  - gl_rubocop.gemspec
164
164
  - lib/gl_rubocop.rb
165
165
  - lib/gl_rubocop/gl_cops/callback_method_names.rb
166
+ - lib/gl_rubocop/gl_cops/consolidate_request_system_specs.rb
166
167
  - lib/gl_rubocop/gl_cops/interactor_inherits_from_interactor_base.rb
167
168
  - lib/gl_rubocop/gl_cops/limit_flash_options.rb
168
169
  - lib/gl_rubocop/gl_cops/no_stubbing_perform_async.rb
@@ -172,6 +173,10 @@ files:
172
173
  - lib/gl_rubocop/gl_cops/tailwind_no_contradicting_class_name.rb
173
174
  - lib/gl_rubocop/gl_cops/unique_identifier.rb
174
175
  - lib/gl_rubocop/gl_cops/valid_data_test_id.rb
176
+ - lib/gl_rubocop/gl_cops/vcr_cassette_names.rb
177
+ - lib/gl_rubocop/gl_cops/view_component_class_naming.rb
178
+ - lib/gl_rubocop/gl_cops/view_component_directory_structure.rb
179
+ - lib/gl_rubocop/gl_cops/view_component_inheritance.rb
175
180
  - lib/gl_rubocop/gl_cops/view_component_initialize_keyword_args.rb
176
181
  - lib/gl_rubocop/helpers/erb_content_helper.rb
177
182
  - lib/gl_rubocop/helpers/haml_content_helper.rb
@@ -196,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
201
  - !ruby/object:Gem::Version
197
202
  version: '0'
198
203
  requirements: []
199
- rubygems_version: 3.4.19
204
+ rubygems_version: 3.3.26
200
205
  signing_key:
201
206
  specification_version: 4
202
207
  summary: A shareable configuration of Give Lively's rubocop rules.