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,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for Date/Time/DateTime objects passed to Sidekiq job methods.
7
+ #
8
+ # Date and Time objects should be converted to strings or timestamps
9
+ # before being passed to Sidekiq jobs to ensure proper serialization.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # MyJob.perform_async(Time.current)
14
+ # MyJob.perform_async(Date.today)
15
+ # MyJob.perform_async(DateTime.now)
16
+ #
17
+ # # good
18
+ # MyJob.perform_async(Time.current.iso8601)
19
+ # MyJob.perform_async(Date.today.to_s)
20
+ # MyJob.perform_async(Time.current.to_i)
21
+ #
22
+ class DateTimeArgument < Base
23
+ include ArgumentTraversal
24
+
25
+ MSG = 'Do not pass Date/Time objects to Sidekiq jobs. ' \
26
+ 'Convert to a string or timestamp first.'
27
+
28
+ RESTRICT_ON_SEND = PerformMethods.all
29
+
30
+ TIME_METHODS = %i[now current zone].freeze
31
+ DATE_METHODS = %i[today yesterday tomorrow current].freeze
32
+
33
+ # @!method sidekiq_perform_call?(node)
34
+ def_node_matcher :sidekiq_perform_call?, <<~PATTERN
35
+ (send _ {#{RESTRICT_ON_SEND.map(&:inspect).join(' ')}} $...)
36
+ PATTERN
37
+
38
+ # @!method time_constructor?(node)
39
+ def_node_matcher :time_constructor?, <<~PATTERN
40
+ (send (const {nil? cbase} :Time) {#{TIME_METHODS.map(&:inspect).join(' ')}} ...)
41
+ PATTERN
42
+
43
+ # @!method date_constructor?(node)
44
+ def_node_matcher :date_constructor?, <<~PATTERN
45
+ (send (const {nil? cbase} :Date) {#{DATE_METHODS.map(&:inspect).join(' ')}} ...)
46
+ PATTERN
47
+
48
+ # @!method datetime_constructor?(node)
49
+ def_node_matcher :datetime_constructor?, <<~PATTERN
50
+ (send (const {nil? cbase} :DateTime) {:now :current :parse} ...)
51
+ PATTERN
52
+
53
+ def on_send(node)
54
+ sidekiq_perform_call?(node) do |args|
55
+ check_arguments(args)
56
+ end
57
+ end
58
+ alias on_csend on_send
59
+
60
+ private
61
+
62
+ def check_argument(arg)
63
+ if datetime_object?(arg)
64
+ add_offense(arg)
65
+ elsif arg.hash_type?
66
+ check_hash_values(arg)
67
+ elsif arg.array_type?
68
+ check_array_elements(arg)
69
+ end
70
+ end
71
+
72
+ def datetime_object?(node)
73
+ return false unless node.send_type?
74
+
75
+ time_constructor?(node) || date_constructor?(node) || datetime_constructor?(node)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for deprecated Sidekiq.default_worker_options usage.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # Sidekiq.default_worker_options = { retry: 5 }
11
+ #
12
+ # # good
13
+ # Sidekiq.default_job_options = { retry: 5 }
14
+ #
15
+ class DeprecatedDefaultWorkerOptions < Base
16
+ extend AutoCorrector
17
+
18
+ MSG = 'Sidekiq.default_worker_options is deprecated. Use Sidekiq.default_job_options instead.'
19
+
20
+ RESTRICT_ON_SEND = %i[default_worker_options default_worker_options=].freeze
21
+
22
+ def on_send(node)
23
+ return unless sidekiq_receiver?(node)
24
+
25
+ add_offense(node.loc.selector) do |corrector|
26
+ corrector.replace(node.loc.selector, replacement_selector(node))
27
+ end
28
+ end
29
+ alias on_csend on_send
30
+
31
+ private
32
+
33
+ def sidekiq_receiver?(node)
34
+ receiver = node.receiver
35
+ receiver&.const_type? && receiver.const_name == 'Sidekiq'
36
+ end
37
+
38
+ def replacement_selector(_node)
39
+ 'default_job_options'
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for deprecated delay extension usage.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # UserMailer.delay.welcome_email(user)
11
+ #
12
+ # # good
13
+ # UserMailer.welcome_email(user).deliver_later
14
+ #
15
+ class DeprecatedDelayExtension < Base
16
+ MSG = 'Avoid using the delay extension. Use `deliver_later` or enqueue a Sidekiq job instead.'
17
+
18
+ RESTRICT_ON_SEND = %i[delay].freeze
19
+
20
+ def on_send(node)
21
+ return unless node.arguments.empty?
22
+
23
+ add_offense(node.loc.selector)
24
+ end
25
+ alias on_csend on_send
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for Sidekiq::Worker usage and recommends Sidekiq::Job.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # class MyWorker
11
+ # include Sidekiq::Worker
12
+ # end
13
+ #
14
+ # # good
15
+ # class MyJob
16
+ # include Sidekiq::Job
17
+ # end
18
+ #
19
+ class DeprecatedWorkerModule < Base
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Sidekiq::Worker is deprecated. Use Sidekiq::Job instead.'
23
+
24
+ # @!method sidekiq_worker_include?(node)
25
+ def_node_matcher :sidekiq_worker_include?, <<~PATTERN
26
+ (send nil? :include (const (const {nil? cbase} :Sidekiq) :Worker))
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ sidekiq_worker_include?(node) do
31
+ add_offense(node) do |corrector|
32
+ corrector.replace(node, 'include Sidekiq::Job')
33
+ end
34
+ end
35
+ end
36
+ alias on_csend on_send
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for perform_async calls inside loops and recommends perform_bulk.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # users.each do |user|
11
+ # NotifyJob.perform_async(user.id)
12
+ # end
13
+ #
14
+ # # good
15
+ # user_ids = users.map { |user| [user.id] }
16
+ # NotifyJob.perform_bulk(user_ids)
17
+ #
18
+ class EnqueueInefficiency < Base
19
+ MSG = 'Prefer `perform_bulk` over `perform_async` inside loops to reduce Redis round trips.'
20
+
21
+ DEFAULT_ALLOWED_METHODS = %w[each find_each find_in_batches].freeze
22
+
23
+ def on_block(node)
24
+ send_node = node.send_node
25
+ return unless allowed_method?(send_node)
26
+ return unless meets_minimum_iterations?(send_node)
27
+
28
+ check_send(node.body)
29
+ end
30
+
31
+ def on_numblock(node)
32
+ on_block(node)
33
+ end
34
+
35
+ private
36
+
37
+ def check_send(body_node)
38
+ return unless body_node
39
+
40
+ add_offense(body_node) if body_node.send_type? && body_node.method?(:perform_async)
41
+
42
+ body_node.each_descendant(:send) do |send|
43
+ next unless send.method?(:perform_async)
44
+
45
+ add_offense(send)
46
+ end
47
+ end
48
+
49
+ def allowed_method?(send_node)
50
+ allowed_methods = cop_config.fetch('AllowedMethods', DEFAULT_ALLOWED_METHODS)
51
+ allowed_methods.include?(send_node.method_name.to_s)
52
+ end
53
+
54
+ def meets_minimum_iterations?(send_node)
55
+ minimum = cop_config['MinimumIterations']
56
+ return true unless minimum
57
+
58
+ iteration_count = iteration_count(send_node)
59
+ return true unless iteration_count
60
+
61
+ iteration_count >= minimum
62
+ end
63
+
64
+ def iteration_count(send_node)
65
+ return unless send_node.method?(:times)
66
+
67
+ receiver = send_node.receiver
68
+ return unless receiver&.int_type?
69
+
70
+ receiver.value
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for excessive retry counts in sidekiq_options.
7
+ #
8
+ # @example MaxRetries: 25 (default)
9
+ # # bad
10
+ # sidekiq_options retry: 100
11
+ #
12
+ class ExcessiveRetry < Base
13
+ MSG = 'Retry count exceeds the maximum allowed (%<max>d).'
14
+
15
+ def on_send(node)
16
+ sidekiq_options_call?(node) do |args|
17
+ args.each { |arg| check_hash(arg) }
18
+ end
19
+ end
20
+ alias on_csend on_send
21
+
22
+ private
23
+
24
+ def check_hash(arg)
25
+ return unless arg.hash_type?
26
+
27
+ arg.pairs.each do |pair|
28
+ check_pair(pair)
29
+ end
30
+ end
31
+
32
+ def check_pair(pair)
33
+ return unless retry_key?(pair.key)
34
+
35
+ value = pair.value
36
+ return unless value&.int_type?
37
+ return unless value.value > max_retries
38
+
39
+ add_offense(value, message: format(MSG, max: max_retries))
40
+ end
41
+
42
+ def retry_key?(node)
43
+ node&.sym_type? && node.value == :retry
44
+ end
45
+
46
+ def max_retries
47
+ cop_config.fetch('MaxRetries', 25)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for find_each/find_in_batches usage inside Sidekiq job perform methods.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # class ProcessAllUsersJob
11
+ # include Sidekiq::Job
12
+ #
13
+ # def perform
14
+ # User.find_each { |user| process(user) }
15
+ # end
16
+ # end
17
+ #
18
+ # # good
19
+ # class ProcessUserJob
20
+ # include Sidekiq::Job
21
+ #
22
+ # def perform(user_id)
23
+ # user = User.find(user_id)
24
+ # process(user)
25
+ # end
26
+ # end
27
+ #
28
+ class FindEachInJob < Base
29
+ MSG = 'Avoid processing large datasets in a single Sidekiq job. Split into smaller jobs instead.'
30
+
31
+ DEFAULT_ALLOWED_METHODS = [].freeze
32
+ RESTRICT_ON_SEND = %i[find_each find_in_batches].freeze
33
+
34
+ def on_send(node)
35
+ return unless in_sidekiq_job?(node)
36
+ return unless in_perform_method?(node)
37
+ return if allowed_method?(node)
38
+
39
+ add_offense(node)
40
+ end
41
+ alias on_csend on_send
42
+
43
+ private
44
+
45
+ def in_perform_method?(node)
46
+ node.each_ancestor(:any_def).any? do |def_node|
47
+ def_node.method?(:perform)
48
+ end
49
+ end
50
+
51
+ def allowed_method?(node)
52
+ allowed_methods = cop_config.fetch('AllowedMethods', DEFAULT_ALLOWED_METHODS)
53
+ allowed_methods.include?(node.method_name.to_s)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for potentially huge arguments passed to Sidekiq jobs.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # MyJob.perform_async(users.pluck(:id, :name, :email))
11
+ # MyJob.perform_async([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
12
+ #
13
+ # # good
14
+ # MyJob.perform_async(user_ids)
15
+ #
16
+ class HugeJobArguments < Base
17
+ MSG = 'Avoid passing large arguments to Sidekiq jobs. Pass IDs and load records in the job instead.'
18
+
19
+ DEFAULT_MAX_ARRAY_SIZE = 10
20
+ DEFAULT_MAX_HASH_SIZE = 10
21
+ DEFAULT_MAX_PLUCK_COLUMNS = 3
22
+
23
+ def on_send(node)
24
+ perform_call?(node) do
25
+ node.arguments.each do |arg|
26
+ check_argument(arg)
27
+ end
28
+ end
29
+ end
30
+ alias on_csend on_send
31
+
32
+ private
33
+
34
+ def check_argument(arg)
35
+ return unless large_pluck?(arg) || large_array?(arg) || large_hash?(arg)
36
+
37
+ add_offense(arg)
38
+ end
39
+
40
+ def large_pluck?(arg)
41
+ return false unless arg.send_type?
42
+ return false unless arg.method?(:pluck)
43
+
44
+ arg.arguments.size >= max_pluck_columns
45
+ end
46
+
47
+ def large_array?(arg)
48
+ return false unless arg.array_type?
49
+
50
+ arg.values.size > max_array_size
51
+ end
52
+
53
+ def large_hash?(arg)
54
+ return false unless arg.hash_type?
55
+
56
+ arg.pairs.size > max_hash_size
57
+ end
58
+
59
+ def max_array_size
60
+ cop_config.fetch('MaxArraySize', DEFAULT_MAX_ARRAY_SIZE)
61
+ end
62
+
63
+ def max_hash_size
64
+ cop_config.fetch('MaxHashSize', DEFAULT_MAX_HASH_SIZE)
65
+ end
66
+
67
+ def max_pluck_columns
68
+ cop_config.fetch('MaxPluckColumns', DEFAULT_MAX_PLUCK_COLUMNS)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for implicit job dependencies (enqueuing other jobs inside jobs).
7
+ #
8
+ # @example
9
+ # # bad
10
+ # def perform
11
+ # SecondJob.perform_async
12
+ # end
13
+ #
14
+ class JobDependency < Base
15
+ MSG = 'Avoid implicit job dependencies. Use Sidekiq Batches instead.'
16
+
17
+ def on_def(node)
18
+ return unless node.method?(:perform)
19
+ return unless in_sidekiq_job?(node)
20
+
21
+ node.each_descendant(:send) do |send|
22
+ next unless perform_call?(send)
23
+
24
+ add_offense(send)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks that Sidekiq job classes are located in allowed directories.
7
+ #
8
+ # @example
9
+ # # bad - app/models/send_email_job.rb
10
+ # class SendEmailJob
11
+ # include Sidekiq::Job
12
+ # end
13
+ #
14
+ # # good - app/jobs/send_email_job.rb
15
+ # class SendEmailJob
16
+ # include Sidekiq::Job
17
+ # end
18
+ #
19
+ class JobFileLocation < Base
20
+ include ProcessedSourcePath
21
+
22
+ MSG = 'Place Sidekiq job classes under %<dirs>s.'
23
+
24
+ DEFAULT_DIRECTORIES = ['app/jobs', 'app/workers'].freeze
25
+
26
+ def on_class(node)
27
+ return unless sidekiq_job_class?(node)
28
+
29
+ file_path = processed_file_path
30
+ return unless file_path
31
+
32
+ return if in_allowed_directory?(file_path)
33
+
34
+ add_offense(node.loc.keyword, message: format(MSG, dirs: allowed_directories.join(' or ')))
35
+ end
36
+
37
+ private
38
+
39
+ def allowed_directories
40
+ cop_config.fetch('AllowedDirectories', DEFAULT_DIRECTORIES)
41
+ end
42
+
43
+ def in_allowed_directory?(file_path)
44
+ allowed_directories.any? do |dir|
45
+ file_path.include?("/#{dir}/") || file_path.include?("#{dir}/") ||
46
+ file_path.end_with?("/#{dir}") || file_path.end_with?(dir)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks that job file names match the class name.
7
+ #
8
+ # @example
9
+ # # bad - file: send_email_worker.rb
10
+ # class SendEmailJob
11
+ # include Sidekiq::Job
12
+ # end
13
+ #
14
+ class JobFileNaming < Base
15
+ include ClassNameHelper
16
+ include ProcessedSourcePath
17
+
18
+ MSG = 'Job file name should match the class name.'
19
+
20
+ def on_class(node)
21
+ return unless sidekiq_job_class?(node)
22
+
23
+ file_path = processed_file_path
24
+ return unless file_path
25
+
26
+ class_name = class_name(node)
27
+ return unless class_name
28
+
29
+ expected = underscore(class_name)
30
+ actual = File.basename(file_path, '.rb')
31
+ return if expected == actual
32
+
33
+ add_offense(node.loc.keyword)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks that Sidekiq jobs include the preferred module.
7
+ #
8
+ # Sidekiq::Worker was renamed to Sidekiq::Job in Sidekiq 6.3.
9
+ # Sidekiq::Job is the preferred module name.
10
+ #
11
+ # @example PreferredModule: Job (default)
12
+ # # bad
13
+ # class MyJob
14
+ # include Sidekiq::Worker
15
+ # end
16
+ #
17
+ # # good
18
+ # class MyJob
19
+ # include Sidekiq::Job
20
+ # end
21
+ #
22
+ # @example PreferredModule: Worker
23
+ # # bad
24
+ # class MyJob
25
+ # include Sidekiq::Job
26
+ # end
27
+ #
28
+ # # good
29
+ # class MyJob
30
+ # include Sidekiq::Worker
31
+ # end
32
+ #
33
+ class JobInclude < Base
34
+ extend AutoCorrector
35
+
36
+ MSG = 'Use `Sidekiq::%<preferred>s` instead of `Sidekiq::%<deprecated>s`.'
37
+
38
+ MODULES = %w[Job Worker].freeze
39
+
40
+ # @!method sidekiq_include(node)
41
+ def_node_matcher :sidekiq_include, <<~PATTERN
42
+ (send nil? :include (const (const {nil? cbase} :Sidekiq) ${:Job :Worker}))
43
+ PATTERN
44
+
45
+ def on_send(node)
46
+ sidekiq_include(node) do |module_name|
47
+ return if module_name.to_s == preferred_module
48
+
49
+ deprecated = module_name.to_s
50
+ message = format(MSG, preferred: preferred_module, deprecated: deprecated)
51
+
52
+ add_offense(node, message: message) do |corrector|
53
+ corrector.replace(node, "include Sidekiq::#{preferred_module}")
54
+ end
55
+ end
56
+ end
57
+ alias on_csend on_send
58
+
59
+ private
60
+
61
+ def preferred_module
62
+ cop_config.fetch('PreferredModule', 'Job')
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sidekiq
6
+ # Checks for missing logging in Sidekiq job perform methods.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # def perform(user_id)
11
+ # do_work(user_id)
12
+ # end
13
+ #
14
+ # # good
15
+ # def perform(user_id)
16
+ # logger.info "Starting job for #{user_id}"
17
+ # do_work(user_id)
18
+ # end
19
+ #
20
+ class MissingLogging < Base
21
+ MSG = 'Add logging to Sidekiq job perform methods.'
22
+
23
+ def on_def(node)
24
+ return unless node.method?(:perform)
25
+ return unless in_sidekiq_job?(node)
26
+ return if logger_call?(node)
27
+
28
+ add_offense(node.loc.keyword)
29
+ end
30
+
31
+ private
32
+
33
+ def logger_call?(def_node)
34
+ def_node.each_descendant(:send).any? { |send| logger_call_send?(send) }
35
+ end
36
+
37
+ def logger_call_send?(send)
38
+ receiver = send.receiver
39
+ return false unless receiver&.send_type?
40
+
41
+ receiver_name = receiver.method_name
42
+ return false unless %i[logger].include?(receiver_name)
43
+
44
+ %i[debug info warn error fatal].include?(send.method_name)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end