carwow_rubocop 6.7.0 → 6.8.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: e3188042457455f9b00b2eba182c978d7687a7324c02bf2df68dd72a353e5a72
4
- data.tar.gz: 15b8d392add3a85e05b87a6acb705d3c1a8cd54f906d7021a5c5a854387a863a
3
+ metadata.gz: 9ba532ee22497e6dad682fcd4d4c8f452faf929df1ce5284e3fb5b2a1d3e5ab8
4
+ data.tar.gz: b54f7cc75cf53f47012ffde9f5253c06c33cc959fe6f38c7aed526aff30f990c
5
5
  SHA512:
6
- metadata.gz: e156ae2a9536b74791994e18aa3e708c9dc083b6018613e696fb564bfb8b0b7aab64a4611b97ab7d9b8149942a975f6846e6a789c4cfa8d8debe10a2e45b8f74
7
- data.tar.gz: 7853d1d241b1d1be66719736a202bfb83a68ebb7677e04b5b4775113c29225a233671cb002b98b5e13e007d50b92b9b89ee5be9eaf0acaf5c8c8e9156c50095d
6
+ metadata.gz: 7f6d34e7a2768754b1b6171aa09dba55b2a3eff404bc93d6039794630f9440c04304615de5ade41d82e58dcefa8f6ff98396053b68a3dee5da6e104746f49084
7
+ data.tar.gz: 47a4d2cfd627a6fda855df105eb4e6ccc8b246e602badaf823f253c9ddf3952aa6deca294c90c85e9dd1dc3eb1284846291880663dbce6bead17ffb187fa518d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- carwow_rubocop (6.7.0)
4
+ carwow_rubocop (6.8.0)
5
5
  rubocop (>= 1.75)
6
6
  rubocop-factory_bot
7
7
  rubocop-performance
@@ -45,3 +45,11 @@ Carwow/NoChangeMatcherWithBrowserAction:
45
45
  Include:
46
46
  - "**/*_spec.rb"
47
47
  - "**/spec/**/*"
48
+
49
+ Carwow/ProductAreaRequired:
50
+ Enabled: true
51
+ Include:
52
+ - "app/controllers/**/*.rb"
53
+ - "app/jobs/**/*.rb"
54
+ Exclude:
55
+ - "**/concerns/**/*.rb"
@@ -1,5 +1,5 @@
1
1
  module RuboCop
2
2
  module Carwow
3
- VERSION = '6.7.0'.freeze
3
+ VERSION = '6.8.0'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Carwow
8
+ # Controllers and jobs are attributed to a team via the `product_area` DSL
9
+ # (CarwowCore::ProductArea::Taggable) so their Honeycomb spans and Bugsnag
10
+ # errors route to the owning team automatically. A class with no declared
11
+ # area falls back to manual triage.
12
+ #
13
+ # This only checks that `product_area` is called somewhere in the class's
14
+ # own body — it cannot verify, statically, that a superclass declared in
15
+ # another file already sets one. Shared base classes that are
16
+ # intentionally left untagged (e.g. ApplicationController, ApplicationJob,
17
+ # or a namespaced API base controller) should be added to this cop's
18
+ # `Exclude` in the consuming app's .rubocop.yml rather than tagged.
19
+ #
20
+ # A class nested inside another class (rather than merely inside a
21
+ # namespacing module) is assumed to be a helper — e.g. a custom error
22
+ # raised by the enclosing job — not an entry point of its own, so it is
23
+ # not checked. Only the enclosing class needs the tag.
24
+ #
25
+ # A class that subclasses a known Ruby exception (StandardError,
26
+ # RuntimeError, ...) or whose superclass name ends in `Error`/`Exception`
27
+ # is never checked, wherever it's defined: exceptions are raised inside
28
+ # an entry point, not routed to one of their own.
29
+ #
30
+ # A class that doesn't look like a controller/job entry point itself (no
31
+ # Controller/Job-ish superclass, no `include Sidekiq::Worker`/`Sidekiq::Job`,
32
+ # no `perform` method) is skipped if it sits alongside a sibling class in
33
+ # the same file/module that already declares `product_area` — a common
34
+ # pattern for small POROs colocated with the controller/job that uses
35
+ # them. Two genuinely independent entry points defined in the same file
36
+ # still both need their own tag.
37
+ #
38
+ # @example
39
+ # # bad
40
+ # class PaymentsController < ApplicationController
41
+ # end
42
+ #
43
+ # # good
44
+ # class PaymentsController < ApplicationController
45
+ # product_area 'smc-payments'
46
+ # end
47
+ #
48
+ # # good - nested error class does not need its own tag
49
+ # class SyncListing < ApplicationJob
50
+ # product_area 'salesforce'
51
+ #
52
+ # class UnknownReference < StandardError; end
53
+ # end
54
+ #
55
+ # # good - sibling error class, and sibling PORO helper, do not need their own tag
56
+ # module Payments
57
+ # class RefundFailed < StandardError; end
58
+ #
59
+ # class RefundsController < ApplicationController
60
+ # product_area 'smc-payments'
61
+ # end
62
+ #
63
+ # class RefundPresenter
64
+ # def initialize(refund); end
65
+ # end
66
+ # end
67
+ #
68
+ class ProductAreaRequired < ::RuboCop::Cop::Base
69
+ MSG = 'Declare a `product_area` for this class so its Honeycomb spans ' \
70
+ 'and Bugsnag errors route to the owning team.'
71
+
72
+ KNOWN_ERROR_SUPERCLASSES = %w[StandardError RuntimeError Exception ScriptError].freeze
73
+ ERROR_NAME_PATTERN = /Error\z|Exception\z/
74
+ ENTRY_POINT_SUPERCLASS_PATTERN = /Controller|Job/
75
+
76
+ def_node_matcher :product_area_declaration?, <<~PATTERN
77
+ (send nil? :product_area _)
78
+ PATTERN
79
+
80
+ def_node_matcher :sidekiq_worker_include?, <<~PATTERN
81
+ (send nil? :include (const (const nil? :Sidekiq) {:Worker :Job}))
82
+ PATTERN
83
+
84
+ def_node_search :defines_perform_method?, <<~PATTERN
85
+ (def :perform ...)
86
+ PATTERN
87
+
88
+ def on_class(node)
89
+ return if nested_in_class?(node)
90
+ return if subclasses_known_error?(node)
91
+ return if declares_product_area?(node)
92
+ return if !looks_like_entry_point?(node) && sibling_already_tagged?(node)
93
+
94
+ class_node, = *node
95
+ add_offense(class_node)
96
+ end
97
+
98
+ private
99
+
100
+ def nested_in_class?(node)
101
+ node.each_ancestor(:class).any?
102
+ end
103
+
104
+ def subclasses_known_error?(class_node)
105
+ _name, superclass, = *class_node
106
+ return false unless superclass
107
+
108
+ superclass_name = superclass.source
109
+
110
+ KNOWN_ERROR_SUPERCLASSES.include?(superclass_name) || superclass_name.match?(ERROR_NAME_PATTERN)
111
+ end
112
+
113
+ def declares_product_area?(class_node)
114
+ top_level_statements(class_node).any? { |stmt| product_area_declaration?(stmt) }
115
+ end
116
+
117
+ def looks_like_entry_point?(class_node)
118
+ _name, superclass, body = *class_node
119
+
120
+ return true if superclass&.source&.match?(ENTRY_POINT_SUPERCLASS_PATTERN)
121
+
122
+ statements = top_level_statements(class_node)
123
+ statements.any? { |stmt| sidekiq_worker_include?(stmt) } || (body && defines_perform_method?(body))
124
+ end
125
+
126
+ def sibling_already_tagged?(class_node)
127
+ siblings_scope = class_node.each_ancestor(:module, :class, :begin).first || class_node.parent
128
+ return false unless siblings_scope
129
+
130
+ sibling_classes(siblings_scope, class_node).any? { |sibling| declares_product_area?(sibling) }
131
+ end
132
+
133
+ def sibling_classes(scope, exclude)
134
+ statements = scope.begin_type? ? scope.children : [scope]
135
+ statements.select { |stmt| stmt&.class_type? && stmt != exclude }
136
+ end
137
+
138
+ def top_level_statements(class_node)
139
+ body = class_node.body
140
+ return [] unless body
141
+
142
+ body.begin_type? ? body.children : [body]
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carwow_rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.7.0
4
+ version: 6.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - carwow Developers
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-15 00:00:00.000000000 Z
11
+ date: 2026-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -172,6 +172,7 @@ files:
172
172
  - lib/rubocop/cop/carwow/no_change_matcher_with_browser_action.rb
173
173
  - lib/rubocop/cop/carwow/no_stubbing_business_event.rb
174
174
  - lib/rubocop/cop/carwow/no_vehicle_brand_model_sideload.rb
175
+ - lib/rubocop/cop/carwow/product_area_required.rb
175
176
  homepage: https://github.com/carwow/carwow_rubocop
176
177
  licenses:
177
178
  - MIT
@@ -192,7 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
193
  - !ruby/object:Gem::Version
193
194
  version: '0'
194
195
  requirements: []
195
- rubygems_version: 3.4.19
196
+ rubygems_version: 3.5.20
196
197
  signing_key:
197
198
  specification_version: 4
198
199
  summary: carwow's rubocop configuration