rubocop-sidekiq_plus 0.1.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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +322 -0
  4. data/config/default.yml +396 -0
  5. data/lib/rubocop/cop/sidekiq/active_record_argument.rb +77 -0
  6. data/lib/rubocop/cop/sidekiq/async_in_test.rb +42 -0
  7. data/lib/rubocop/cop/sidekiq/base.rb +14 -0
  8. data/lib/rubocop/cop/sidekiq/consistent_job_suffix.rb +44 -0
  9. data/lib/rubocop/cop/sidekiq/constant_job_class_name.rb +53 -0
  10. data/lib/rubocop/cop/sidekiq/database_connection_leak.rb +33 -0
  11. data/lib/rubocop/cop/sidekiq/date_time_argument.rb +80 -0
  12. data/lib/rubocop/cop/sidekiq/deprecated_default_worker_options.rb +44 -0
  13. data/lib/rubocop/cop/sidekiq/deprecated_delay_extension.rb +29 -0
  14. data/lib/rubocop/cop/sidekiq/deprecated_worker_module.rb +40 -0
  15. data/lib/rubocop/cop/sidekiq/enqueue_inefficiency.rb +75 -0
  16. data/lib/rubocop/cop/sidekiq/excessive_retry.rb +52 -0
  17. data/lib/rubocop/cop/sidekiq/find_each_in_job.rb +58 -0
  18. data/lib/rubocop/cop/sidekiq/huge_job_arguments.rb +73 -0
  19. data/lib/rubocop/cop/sidekiq/job_dependency.rb +30 -0
  20. data/lib/rubocop/cop/sidekiq/job_file_location.rb +52 -0
  21. data/lib/rubocop/cop/sidekiq/job_file_naming.rb +38 -0
  22. data/lib/rubocop/cop/sidekiq/job_include.rb +67 -0
  23. data/lib/rubocop/cop/sidekiq/missing_logging.rb +49 -0
  24. data/lib/rubocop/cop/sidekiq/missing_timeout.rb +65 -0
  25. data/lib/rubocop/cop/sidekiq/mixed_retry_strategies.rb +55 -0
  26. data/lib/rubocop/cop/sidekiq/mixin/argument_traversal.rb +28 -0
  27. data/lib/rubocop/cop/sidekiq/mixin/class_name_helper.rb +23 -0
  28. data/lib/rubocop/cop/sidekiq/mixin/processed_source_path.rb +19 -0
  29. data/lib/rubocop/cop/sidekiq/no_rescue_all.rb +70 -0
  30. data/lib/rubocop/cop/sidekiq/perform_inline_usage.rb +53 -0
  31. data/lib/rubocop/cop/sidekiq/perform_method_parameters.rb +53 -0
  32. data/lib/rubocop/cop/sidekiq/pii_in_arguments.rb +90 -0
  33. data/lib/rubocop/cop/sidekiq/puts_or_print_usage.rb +37 -0
  34. data/lib/rubocop/cop/sidekiq/queue_specified.rb +74 -0
  35. data/lib/rubocop/cop/sidekiq/redis_in_job.rb +32 -0
  36. data/lib/rubocop/cop/sidekiq/retry_specified.rb +81 -0
  37. data/lib/rubocop/cop/sidekiq/retry_zero.rb +48 -0
  38. data/lib/rubocop/cop/sidekiq/self_scheduling_job.rb +54 -0
  39. data/lib/rubocop/cop/sidekiq/sensitive_data_in_arguments.rb +92 -0
  40. data/lib/rubocop/cop/sidekiq/sidekiq_over_active_job.rb +30 -0
  41. data/lib/rubocop/cop/sidekiq/silent_rescue.rb +63 -0
  42. data/lib/rubocop/cop/sidekiq/sleep_in_jobs.rb +46 -0
  43. data/lib/rubocop/cop/sidekiq/symbol_argument.rb +69 -0
  44. data/lib/rubocop/cop/sidekiq/thread_in_job.rb +53 -0
  45. data/lib/rubocop/cop/sidekiq/transaction_leak.rb +72 -0
  46. data/lib/rubocop/cop/sidekiq/unknown_sidekiq_option.rb +52 -0
  47. data/lib/rubocop/cop/sidekiq_cops.rb +71 -0
  48. data/lib/rubocop/cop/sidekiq_ent/base.rb +43 -0
  49. data/lib/rubocop/cop/sidekiq_ent/encryption_with_many_arguments.rb +71 -0
  50. data/lib/rubocop/cop/sidekiq_ent/encryption_without_secret_bag.rb +83 -0
  51. data/lib/rubocop/cop/sidekiq_ent/leader_election_without_block.rb +75 -0
  52. data/lib/rubocop/cop/sidekiq_ent/limiter_not_reused.rb +79 -0
  53. data/lib/rubocop/cop/sidekiq_ent/limiter_without_lock_timeout.rb +46 -0
  54. data/lib/rubocop/cop/sidekiq_ent/limiter_without_wait_timeout.rb +49 -0
  55. data/lib/rubocop/cop/sidekiq_ent/periodic_job_invalid_cron.rb +108 -0
  56. data/lib/rubocop/cop/sidekiq_ent/periodic_job_with_arguments.rb +94 -0
  57. data/lib/rubocop/cop/sidekiq_ent/unique_job_too_short_ttl.rb +80 -0
  58. data/lib/rubocop/cop/sidekiq_ent/unique_job_without_ttl.rb +52 -0
  59. data/lib/rubocop/cop/sidekiq_ent/unique_until_mismatch.rb +59 -0
  60. data/lib/rubocop/cop/sidekiq_pro/base.rb +39 -0
  61. data/lib/rubocop/cop/sidekiq_pro/batch_callback_method.rb +66 -0
  62. data/lib/rubocop/cop/sidekiq_pro/batch_retry_in_callback.rb +54 -0
  63. data/lib/rubocop/cop/sidekiq_pro/batch_status_polling.rb +68 -0
  64. data/lib/rubocop/cop/sidekiq_pro/batch_without_callback.rb +92 -0
  65. data/lib/rubocop/cop/sidekiq_pro/empty_batch.rb +108 -0
  66. data/lib/rubocop/cop/sidekiq_pro/expiring_job_without_ttl.rb +101 -0
  67. data/lib/rubocop/cop/sidekiq_pro/large_argument_in_batch.rb +93 -0
  68. data/lib/rubocop/cop/sidekiq_pro/nested_batch_without_parent.rb +55 -0
  69. data/lib/rubocop/cop/sidekiq_pro/reliability_not_enabled.rb +67 -0
  70. data/lib/rubocop/sidekiq/config_formatter.rb +60 -0
  71. data/lib/rubocop/sidekiq/description_extractor.rb +70 -0
  72. data/lib/rubocop/sidekiq/language.rb +79 -0
  73. data/lib/rubocop/sidekiq/plugin.rb +30 -0
  74. data/lib/rubocop/sidekiq/version.rb +7 -0
  75. data/lib/rubocop/sidekiq.rb +10 -0
  76. data/lib/rubocop-sidekiq.rb +5 -0
  77. data/lib/rubocop-sidekiq_plus.rb +9 -0
  78. metadata +150 -0
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for retry: 0 usage and suggests retry: false for clarity.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # sidekiq_options retry: 0
11
+ #
12
+ # # good
13
+ # sidekiq_options retry: false
14
+ #
15
+ class RetryZero < Base
16
+ MSG = 'Use `retry: false` instead of `retry: 0` for clarity.'
17
+
18
+ def on_send(node)
19
+ sidekiq_options_call?(node) do |args|
20
+ args.each { |arg| check_hash(arg) }
21
+ end
22
+ end
23
+ alias on_csend on_send
24
+
25
+ private
26
+
27
+ def check_hash(arg)
28
+ return unless arg.hash_type?
29
+
30
+ arg.pairs.each { |pair| check_pair(pair) }
31
+ end
32
+
33
+ def check_pair(pair)
34
+ return unless retry_key?(pair.key)
35
+
36
+ value = pair.value
37
+ return unless value&.int_type? && value.value.zero?
38
+
39
+ add_offense(value)
40
+ end
41
+
42
+ def retry_key?(node)
43
+ node&.sym_type? && node.value == :retry
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for jobs that reschedule themselves.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # def perform
11
+ # self.class.perform_in(1.hour)
12
+ # end
13
+ #
14
+ class SelfSchedulingJob < Base
15
+ include ClassNameHelper
16
+
17
+ MSG = 'Avoid self-scheduling jobs. Use Sidekiq Cron or scheduler instead.'
18
+
19
+ def on_def(node)
20
+ return unless node.method?(:perform)
21
+ return unless in_sidekiq_job?(node)
22
+
23
+ class_node = node.each_ancestor(:class).first
24
+ class_name = class_name(class_node)
25
+
26
+ node.each_descendant(:send) do |send|
27
+ next unless PerformMethods.all.include?(send.method_name)
28
+ next unless self_receiver?(send.receiver, class_name)
29
+
30
+ add_offense(send)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def self_receiver?(receiver, class_name)
37
+ return false unless receiver
38
+
39
+ self_class_receiver?(receiver) || const_self_receiver?(receiver, class_name)
40
+ end
41
+
42
+ def self_class_receiver?(receiver)
43
+ receiver.send_type? &&
44
+ receiver.method?(:class) &&
45
+ receiver.receiver&.self_type?
46
+ end
47
+
48
+ def const_self_receiver?(receiver, class_name)
49
+ receiver.const_type? && class_name && receiver.const_name.split('::').last == class_name
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for sensitive data passed as Sidekiq job arguments.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # UserJob.perform_async(user_id, password)
11
+ #
12
+ # # good
13
+ # UserJob.perform_async(user_id)
14
+ #
15
+ class SensitiveDataInArguments < Base
16
+ include ArgumentTraversal
17
+
18
+ MSG = 'Avoid passing sensitive data in Sidekiq job arguments.'
19
+
20
+ DEFAULT_PATTERNS = %w[
21
+ password passwd pwd token api_key secret credit_card card_number cvv ssn
22
+ ].freeze
23
+
24
+ RESTRICT_ON_SEND = PerformMethods.all
25
+
26
+ def on_send(node)
27
+ perform_call?(node) do
28
+ check_arguments(node.arguments, allow_literal: true)
29
+ end
30
+ end
31
+ alias on_csend on_send
32
+
33
+ private
34
+
35
+ def check_argument(arg, allow_literal:)
36
+ return check_literal(arg, allow_literal) if literal_node?(arg)
37
+ return check_variable(arg) if var_node?(arg)
38
+ return check_send(arg) if arg.send_type?
39
+ return check_hash(arg) if arg.hash_type?
40
+
41
+ check_array_elements(arg, allow_literal: allow_literal) if arg.array_type?
42
+ end
43
+
44
+ def literal_node?(arg)
45
+ arg.type?(:sym, :str)
46
+ end
47
+
48
+ def var_node?(arg)
49
+ %i[lvar ivar gvar cvar].include?(arg.type)
50
+ end
51
+
52
+ def check_literal(arg, allow_literal)
53
+ return unless allow_literal
54
+ return unless sensitive_name?(arg.value.to_s)
55
+
56
+ add_offense(arg)
57
+ end
58
+
59
+ def check_variable(arg)
60
+ add_offense(arg) if sensitive_name?(arg.name.to_s)
61
+ end
62
+
63
+ def check_send(arg)
64
+ return unless arg.receiver.nil? && arg.arguments.empty?
65
+ return unless sensitive_name?(arg.method_name.to_s)
66
+
67
+ add_offense(arg)
68
+ end
69
+
70
+ def check_hash(arg)
71
+ arg.pairs.each do |pair|
72
+ check_hash_pair(pair)
73
+ end
74
+ end
75
+
76
+ def check_hash_pair(pair)
77
+ key = pair.key
78
+ add_offense(key) if key&.type?(:sym, :str) && sensitive_name?(key.value.to_s)
79
+ check_argument(pair.value, allow_literal: false) if pair.value
80
+ end
81
+
82
+ def sensitive_name?(name)
83
+ patterns.any? { |pattern| name.downcase.include?(pattern) }
84
+ end
85
+
86
+ def patterns
87
+ Array(cop_config.fetch('SensitivePatterns', DEFAULT_PATTERNS))
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Recommends using Sidekiq::Job over ActiveJob.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # class MyJob < ApplicationJob
11
+ # def perform; end
12
+ # end
13
+ #
14
+ # # good
15
+ # class MyJob
16
+ # include Sidekiq::Job
17
+ # end
18
+ #
19
+ class SidekiqOverActiveJob < Base
20
+ MSG = 'Prefer Sidekiq::Job over ActiveJob for Sidekiq-specific features.'
21
+
22
+ def on_class(node)
23
+ return unless active_job_class?(node)
24
+
25
+ add_offense(node.loc.keyword)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for rescue blocks that swallow exceptions in Sidekiq jobs.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # def perform
11
+ # do_work
12
+ # rescue => e
13
+ # Rails.logger.error(e)
14
+ # end
15
+ #
16
+ # # good
17
+ # def perform
18
+ # do_work
19
+ # rescue => e
20
+ # Rails.logger.error(e)
21
+ # raise
22
+ # end
23
+ #
24
+ class SilentRescue < Base
25
+ MSG = 'Do not silently swallow exceptions in Sidekiq jobs. Re-raise or handle explicitly.'
26
+ RERAISE_METHODS = %i[raise fail].freeze
27
+
28
+ def on_def(node)
29
+ return unless node.method?(:perform)
30
+ return unless in_sidekiq_job?(node)
31
+
32
+ node.each_descendant(:resbody) do |resbody|
33
+ next if allowed_exception?(resbody)
34
+ next if re_raises?(resbody)
35
+
36
+ add_offense(resbody)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def allowed_exception?(resbody)
43
+ allowed = Array(cop_config.fetch('AllowedExceptions', []))
44
+ exceptions = resbody.exceptions
45
+ return false if exceptions.nil? || exceptions.empty?
46
+
47
+ exceptions.all? do |exception|
48
+ exception.const_type? && allowed.include?(exception.const_name)
49
+ end
50
+ end
51
+
52
+ def re_raises?(resbody)
53
+ body = resbody.body
54
+ return false unless body
55
+
56
+ body.each_descendant(:send).any? do |send|
57
+ send.receiver.nil? && RERAISE_METHODS.include?(send.method_name)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for `sleep` calls inside Sidekiq jobs.
7
+ #
8
+ # Using `sleep` in a Sidekiq job blocks the worker thread and prevents
9
+ # it from processing other jobs. Instead, use `perform_in` or
10
+ # `perform_at` to schedule the job for later.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # class MyJob
15
+ # include Sidekiq::Job
16
+ #
17
+ # def perform
18
+ # sleep 5
19
+ # end
20
+ # end
21
+ #
22
+ # # good - use perform_in instead
23
+ # MyJob.perform_in(5.seconds, args)
24
+ #
25
+ class SleepInJobs < Base
26
+ MSG = 'Do not use `sleep` in Sidekiq jobs. ' \
27
+ 'It blocks the worker thread. Use `perform_in` or `perform_at` instead.'
28
+
29
+ RESTRICT_ON_SEND = %i[sleep].freeze
30
+
31
+ # @!method sleep_call?(node)
32
+ def_node_matcher :sleep_call?, <<~PATTERN
33
+ (send {nil? (const {nil? cbase} :Kernel)} :sleep ...)
34
+ PATTERN
35
+
36
+ def on_send(node)
37
+ return unless sleep_call?(node)
38
+ return unless in_sidekiq_job?(node)
39
+
40
+ add_offense(node)
41
+ end
42
+ alias on_csend on_send
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for symbol arguments passed to Sidekiq job methods.
7
+ #
8
+ # Symbols cannot be properly serialized to JSON and will be converted
9
+ # to strings. This can lead to unexpected behavior.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # MyJob.perform_async(:status)
14
+ # MyJob.perform_async(key: :value)
15
+ # MyJob.perform_in(1.hour, :pending)
16
+ #
17
+ # # good
18
+ # MyJob.perform_async('status')
19
+ # MyJob.perform_async(key: 'value')
20
+ # MyJob.perform_in(1.hour, 'pending')
21
+ #
22
+ class SymbolArgument < Base
23
+ extend AutoCorrector
24
+ include ArgumentTraversal
25
+
26
+ MSG = 'Do not pass symbols to Sidekiq jobs. Use strings instead.'
27
+
28
+ RESTRICT_ON_SEND = PerformMethods.all
29
+
30
+ # @!method sidekiq_perform_call?(node)
31
+ def_node_matcher :sidekiq_perform_call?, <<~PATTERN
32
+ (send _ {#{RESTRICT_ON_SEND.map(&:inspect).join(' ')}} $...)
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ sidekiq_perform_call?(node) do |args|
37
+ check_arguments(args)
38
+ end
39
+ end
40
+ alias on_csend on_send
41
+
42
+ private
43
+
44
+ def check_argument(arg)
45
+ case arg.type
46
+ when :sym
47
+ register_symbol_offense(arg)
48
+ when :dsym
49
+ add_offense(arg)
50
+ when :hash
51
+ check_hash_values(arg)
52
+ when :array
53
+ check_array_elements(arg)
54
+ end
55
+ end
56
+
57
+ def register_symbol_offense(node)
58
+ add_offense(node) do |corrector|
59
+ corrector.replace(node, symbol_to_string(node))
60
+ end
61
+ end
62
+
63
+ def symbol_to_string(node)
64
+ node.value.to_s.inspect
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for thread creation inside Sidekiq jobs.
7
+ #
8
+ # Creating threads inside Sidekiq jobs is problematic because:
9
+ # - Sidekiq already manages its own thread pool
10
+ # - Threads may not complete before the job finishes
11
+ # - It can lead to resource leaks and unexpected behavior
12
+ #
13
+ # @example
14
+ # # bad
15
+ # class MyJob
16
+ # include Sidekiq::Job
17
+ #
18
+ # def perform
19
+ # Thread.new { do_work }
20
+ # end
21
+ # end
22
+ #
23
+ # # good - use separate jobs instead
24
+ # class MyJob
25
+ # include Sidekiq::Job
26
+ #
27
+ # def perform
28
+ # SubJob.perform_async
29
+ # end
30
+ # end
31
+ #
32
+ class ThreadInJob < Base
33
+ MSG = 'Do not create threads inside Sidekiq jobs. ' \
34
+ "Use separate jobs or Sidekiq's built-in concurrency instead."
35
+
36
+ RESTRICT_ON_SEND = %i[new fork].freeze
37
+
38
+ # @!method thread_creation?(node)
39
+ def_node_matcher :thread_creation?, <<~PATTERN
40
+ (send (const {nil? cbase} :Thread) {:new :fork} ...)
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ return unless thread_creation?(node)
45
+ return unless in_sidekiq_job?(node)
46
+
47
+ add_offense(node)
48
+ end
49
+ alias on_csend on_send
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for Sidekiq jobs enqueued inside database transactions.
7
+ #
8
+ # Enqueuing jobs inside a transaction can lead to race conditions where
9
+ # the job runs before the transaction commits, causing the job to see
10
+ # stale data or fail to find the record.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # ActiveRecord::Base.transaction do
15
+ # user.save!
16
+ # NotificationJob.perform_async(user.id)
17
+ # end
18
+ #
19
+ # # good - enqueue after transaction
20
+ # user.save!
21
+ # NotificationJob.perform_async(user.id)
22
+ #
23
+ # # good - use after_commit callback
24
+ # class User < ApplicationRecord
25
+ # after_commit :send_notification, on: :create
26
+ #
27
+ # def send_notification
28
+ # NotificationJob.perform_async(id)
29
+ # end
30
+ # end
31
+ #
32
+ class TransactionLeak < Base
33
+ MSG = 'Do not enqueue Sidekiq jobs inside database transactions. ' \
34
+ 'The job may run before the transaction commits.'
35
+
36
+ RESTRICT_ON_SEND = PerformMethods.all
37
+
38
+ # @!method perform_call?(node)
39
+ def_node_matcher :perform_call?, <<~PATTERN
40
+ (send _ {#{RESTRICT_ON_SEND.map(&:inspect).join(' ')}} ...)
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ return unless perform_call?(node)
45
+ return unless inside_transaction?(node)
46
+
47
+ add_offense(node)
48
+ end
49
+ alias on_csend on_send
50
+
51
+ private
52
+
53
+ def inside_transaction?(node)
54
+ node.each_ancestor(:block).any? do |block_node|
55
+ transaction_block?(block_node)
56
+ end
57
+ end
58
+
59
+ def transaction_block?(block_node)
60
+ return false unless block_node.send_node
61
+
62
+ transaction_call?(block_node.send_node)
63
+ end
64
+
65
+ # @!method transaction_call?(node)
66
+ def_node_matcher :transaction_call?, <<~PATTERN
67
+ (send _ :transaction ...)
68
+ PATTERN
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for unknown or unsupported options passed to sidekiq_options.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # sidekiq_options priorty: :high
11
+ # sidekiq_options unique: true
12
+ #
13
+ # # good
14
+ # sidekiq_options queue: :critical, retry: 5
15
+ #
16
+ class UnknownSidekiqOption < Base
17
+ MSG = 'Unknown or unsupported Sidekiq option `%<option>s` in `sidekiq_options`.'
18
+
19
+ ALLOWED_OPTIONS = %w[queue retry dead backtrace pool tags].freeze
20
+
21
+ def on_send(node)
22
+ sidekiq_options_call?(node) do |args|
23
+ args.each do |arg|
24
+ check_options_hash(arg)
25
+ end
26
+ end
27
+ end
28
+ alias on_csend on_send
29
+
30
+ private
31
+
32
+ def check_options_hash(node)
33
+ return unless node.hash_type?
34
+
35
+ node.pairs.each do |pair|
36
+ option = option_name(pair.key)
37
+ next unless option
38
+ next if ALLOWED_OPTIONS.include?(option)
39
+
40
+ add_offense(pair, message: format(MSG, option: option))
41
+ end
42
+ end
43
+
44
+ def option_name(node)
45
+ return node.value.to_s if node.sym_type?
46
+
47
+ node.value if node.str_type?
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../sidekiq/language'
4
+ require_relative 'sidekiq/base'
5
+ require_relative 'sidekiq/mixin/argument_traversal'
6
+ require_relative 'sidekiq/mixin/class_name_helper'
7
+ require_relative 'sidekiq/mixin/processed_source_path'
8
+ require_relative 'sidekiq/active_record_argument'
9
+ require_relative 'sidekiq/find_each_in_job'
10
+ require_relative 'sidekiq/consistent_job_suffix'
11
+ require_relative 'sidekiq/constant_job_class_name'
12
+ require_relative 'sidekiq/database_connection_leak'
13
+ require_relative 'sidekiq/date_time_argument'
14
+ require_relative 'sidekiq/deprecated_default_worker_options'
15
+ require_relative 'sidekiq/deprecated_delay_extension'
16
+ require_relative 'sidekiq/deprecated_worker_module'
17
+ require_relative 'sidekiq/excessive_retry'
18
+ require_relative 'sidekiq/huge_job_arguments'
19
+ require_relative 'sidekiq/enqueue_inefficiency'
20
+ require_relative 'sidekiq/job_dependency'
21
+ require_relative 'sidekiq/job_file_location'
22
+ require_relative 'sidekiq/job_file_naming'
23
+ require_relative 'sidekiq/missing_logging'
24
+ require_relative 'sidekiq/missing_timeout'
25
+ require_relative 'sidekiq/mixed_retry_strategies'
26
+ require_relative 'sidekiq/perform_method_parameters'
27
+ require_relative 'sidekiq/async_in_test'
28
+ require_relative 'sidekiq/pii_in_arguments'
29
+ require_relative 'sidekiq/sidekiq_over_active_job'
30
+ require_relative 'sidekiq/redis_in_job'
31
+ require_relative 'sidekiq/retry_zero'
32
+ require_relative 'sidekiq/self_scheduling_job'
33
+ require_relative 'sidekiq/sensitive_data_in_arguments'
34
+ require_relative 'sidekiq/silent_rescue'
35
+ require_relative 'sidekiq/symbol_argument'
36
+ require_relative 'sidekiq/thread_in_job'
37
+ require_relative 'sidekiq/job_include'
38
+ require_relative 'sidekiq/sleep_in_jobs'
39
+ require_relative 'sidekiq/no_rescue_all'
40
+ require_relative 'sidekiq/perform_inline_usage'
41
+ require_relative 'sidekiq/queue_specified'
42
+ require_relative 'sidekiq/retry_specified'
43
+ require_relative 'sidekiq/transaction_leak'
44
+ require_relative 'sidekiq/unknown_sidekiq_option'
45
+ require_relative 'sidekiq/puts_or_print_usage'
46
+
47
+ # Sidekiq Pro Cops
48
+ require_relative 'sidekiq_pro/base'
49
+ require_relative 'sidekiq_pro/batch_callback_method'
50
+ require_relative 'sidekiq_pro/batch_retry_in_callback'
51
+ require_relative 'sidekiq_pro/batch_status_polling'
52
+ require_relative 'sidekiq_pro/batch_without_callback'
53
+ require_relative 'sidekiq_pro/empty_batch'
54
+ require_relative 'sidekiq_pro/expiring_job_without_ttl'
55
+ require_relative 'sidekiq_pro/large_argument_in_batch'
56
+ require_relative 'sidekiq_pro/nested_batch_without_parent'
57
+ require_relative 'sidekiq_pro/reliability_not_enabled'
58
+
59
+ # Sidekiq Enterprise Cops
60
+ require_relative 'sidekiq_ent/base'
61
+ require_relative 'sidekiq_ent/encryption_with_many_arguments'
62
+ require_relative 'sidekiq_ent/encryption_without_secret_bag'
63
+ require_relative 'sidekiq_ent/leader_election_without_block'
64
+ require_relative 'sidekiq_ent/limiter_not_reused'
65
+ require_relative 'sidekiq_ent/limiter_without_lock_timeout'
66
+ require_relative 'sidekiq_ent/limiter_without_wait_timeout'
67
+ require_relative 'sidekiq_ent/periodic_job_invalid_cron'
68
+ require_relative 'sidekiq_ent/periodic_job_with_arguments'
69
+ require_relative 'sidekiq_ent/unique_job_too_short_ttl'
70
+ require_relative 'sidekiq_ent/unique_job_without_ttl'
71
+ require_relative 'sidekiq_ent/unique_until_mismatch'
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SidekiqEnt
6
+ # @abstract
7
+ # Base class for Sidekiq Enterprise cops.
8
+ # Provides common functionality for detecting Enterprise-specific patterns.
9
+ class Base < ::RuboCop::Cop::Base
10
+ abstract! if respond_to?(:abstract!)
11
+ include RuboCop::Sidekiq::Language
12
+
13
+ # Detect Sidekiq::Limiter.concurrent/bucket/window/leaky
14
+ # @!method limiter_creation?(node)
15
+ def_node_matcher :limiter_creation?, <<~PATTERN
16
+ (send
17
+ (const (const {nil? cbase} :Sidekiq) :Limiter)
18
+ ${:concurrent :bucket :window :leaky}
19
+ $_
20
+ $_
21
+ ...
22
+ )
23
+ PATTERN
24
+
25
+ # Detect sidekiq_options with unique_for
26
+ # @!method unique_for_option?(node)
27
+ def_node_matcher :unique_for_option?, <<~PATTERN
28
+ (send nil? :sidekiq_options
29
+ (hash <(pair (sym :unique_for) $_) ...>)
30
+ )
31
+ PATTERN
32
+
33
+ # Detect sidekiq_options with unique_until
34
+ # @!method unique_until_option?(node)
35
+ def_node_matcher :unique_until_option?, <<~PATTERN
36
+ (send nil? :sidekiq_options
37
+ (hash <(pair (sym :unique_until) $_) ...>)
38
+ )
39
+ PATTERN
40
+ end
41
+ end
42
+ end
43
+ end