gl_rubocop 0.3.3 → 0.5.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: 8d0d6882bfbd2fb93c58c4c0e3a9302e4f98ac4977aa469e0b40a57856a7cfd0
4
- data.tar.gz: 86ea3ab20f3b8899e6b8806ad1daabf561d732c6ad26c80017102e57c8f67bf1
3
+ metadata.gz: 83a8c15ce9c97fee3534fd6ec1b13efa8c150b4a6a06042f1408be213022c453
4
+ data.tar.gz: aef1892a704ffc69e5b4fc5ecc35d3c45461a8cac740c07a635e09a46811895b
5
5
  SHA512:
6
- metadata.gz: bac9a336b0f9e60517d727a5791f8cf909d824ce978e30dff1bb3a4788d22ed358fff0106e14db757c64c3079a0fa7df7b7cb632df258bce529a963b3d6763d4
7
- data.tar.gz: 7b84eda9ac0ea8feddc5996f499b36c4d0408d729f764b95b882dbdcfdf5fe7a44e08d1e1eaa6dec5e726bfcfd75e45ad88e157d017afc3497ea4e43378ef5a4
6
+ metadata.gz: 796c52aff461d138901b41715b5d677518057ae3cb81715f39036f4e4c92c9c1294732d5e149160addc245eee87065b7fc49ffaad3c936ae0d586f5263dedf42
7
+ data.tar.gz: 35f95f56ace1f7c2e529df369e5d397b7a6cfd741be5a4a7d53fb7a9ffd849b200919343a0006586cb772403b81d5369c3bda5e406fa924de127ec1c656e5f76
data/default.yml CHANGED
@@ -9,14 +9,17 @@ 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
21
+ - ./lib/gl_rubocop/gl_cops/valid_data_test_id.rb
22
+ - ./lib/gl_rubocop/gl_cops/vcr_cassette_names.rb
20
23
  - ./lib/gl_rubocop/gl_cops/view_component_initialize_keyword_args.rb
21
24
 
22
25
  AllCops:
@@ -43,6 +46,11 @@ Capybara/NegationMatcher:
43
46
  GLCops/CallbackMethodNames:
44
47
  Enabled: true
45
48
 
49
+ GLCops/ConsolidateRequestSystemSpecs:
50
+ Enabled: true
51
+ Include:
52
+ - "**/*spec.rb"
53
+
46
54
  GLCops/InteractorInheritsFromInteractorBase:
47
55
  Enabled: true
48
56
  Include:
@@ -76,6 +84,18 @@ GLCops/UniqueIdentifier:
76
84
  Include:
77
85
  - "app/components/**/*.erb"
78
86
 
87
+ GLCops/ValidDataTestId:
88
+ Enabled: true
89
+ Include:
90
+ - "**/*.erb"
91
+ - "**/*.haml"
92
+ - "**/*.rb"
93
+
94
+ GLCops/VcrCassetteNames:
95
+ Enabled: true
96
+ Include:
97
+ - "**/*spec.rb"
98
+
79
99
  GLCops/ViewComponentInitializeKeywordArgs:
80
100
  Enabled: true
81
101
  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,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helpers/haml_content_helper'
4
+ require_relative '../helpers/erb_content_helper'
5
+
6
+ module GLRubocop
7
+ module GLCops
8
+ class ValidDataTestId < RuboCop::Cop::Cop
9
+ include GLRubocop::HamlContentHelper
10
+ include GLRubocop::ErbContentHelper
11
+
12
+ MSG = 'Use data-test-id instead of %<invalid>s'
13
+
14
+ # Invalid variations of data-test-id
15
+ # Matches: data-testid, data_test_id, datatestid, dataTestId, etc.
16
+ # Does NOT match: data-test-id (the valid format)
17
+ INVALID_PATTERN = /\bdata(?!-test-id\b)[-_]?test[-_]?id\b/i
18
+
19
+ def investigate(processed_source)
20
+ return unless haml_file? || erb_file?
21
+
22
+ content = haml_file? ? read_haml_file : read_erb_file
23
+ return unless content
24
+
25
+ check_file_content(content, processed_source)
26
+ end
27
+
28
+ def on_str(node)
29
+ return unless node.str_type?
30
+
31
+ check_string_content(node.value, node)
32
+ end
33
+
34
+ private
35
+
36
+ def check_file_content(content, processed_source)
37
+ return unless content.match?(INVALID_PATTERN)
38
+
39
+ match = content.match(INVALID_PATTERN)
40
+ invalid_attr = match[0].split(/[=:]/).first.gsub(/["']/, '')
41
+ range = processed_source.buffer.source_range
42
+ add_offense(
43
+ nil,
44
+ location: range,
45
+ message: format(MSG, invalid: invalid_attr)
46
+ )
47
+ end
48
+
49
+ def check_string_content(content, node)
50
+ return unless content.match?(INVALID_PATTERN)
51
+
52
+ match = content.match(INVALID_PATTERN)
53
+ invalid_attr = match[0].split(/[=:]/).first.gsub(/["']/, '')
54
+ add_offense(
55
+ node,
56
+ message: format(MSG, invalid: invalid_attr)
57
+ )
58
+ end
59
+ end
60
+ end
61
+ 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
@@ -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.3.3'.freeze
2
+ VERSION = '0.5.0'.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.3.3
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Give Lively
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-11-12 00:00:00.000000000 Z
11
+ date: 2026-03-03 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
@@ -171,6 +172,8 @@ files:
171
172
  - lib/gl_rubocop/gl_cops/sidekiq_inherits_from_sidekiq_job.rb
172
173
  - lib/gl_rubocop/gl_cops/tailwind_no_contradicting_class_name.rb
173
174
  - lib/gl_rubocop/gl_cops/unique_identifier.rb
175
+ - lib/gl_rubocop/gl_cops/valid_data_test_id.rb
176
+ - lib/gl_rubocop/gl_cops/vcr_cassette_names.rb
174
177
  - lib/gl_rubocop/gl_cops/view_component_initialize_keyword_args.rb
175
178
  - lib/gl_rubocop/helpers/erb_content_helper.rb
176
179
  - lib/gl_rubocop/helpers/haml_content_helper.rb
@@ -195,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
198
  - !ruby/object:Gem::Version
196
199
  version: '0'
197
200
  requirements: []
198
- rubygems_version: 3.4.19
201
+ rubygems_version: 3.5.22
199
202
  signing_key:
200
203
  specification_version: 4
201
204
  summary: A shareable configuration of Give Lively's rubocop rules.