rubocop-gusto 10.0.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 +7 -0
- data/CHANGELOG.md +4 -0
- data/LICENSE +21 -0
- data/README.md +53 -0
- data/config/default.yml +781 -0
- data/config/rails.yml +122 -0
- data/exe/gusto-rubocop +12 -0
- data/exe/rubocop-gusto +9 -0
- data/lib/rubocop/cop/gusto/bootsnap_load_file.rb +57 -0
- data/lib/rubocop/cop/gusto/datadog_constant.rb +16 -0
- data/lib/rubocop/cop/gusto/execute_migration.rb +16 -0
- data/lib/rubocop/cop/gusto/factory_classes_or_modules.rb +19 -0
- data/lib/rubocop/cop/gusto/min_by_max_by.rb +45 -0
- data/lib/rubocop/cop/gusto/no_metaprogramming.rb +131 -0
- data/lib/rubocop/cop/gusto/no_rescue_error_message_checking.rb +66 -0
- data/lib/rubocop/cop/gusto/no_send.rb +32 -0
- data/lib/rubocop/cop/gusto/object_in.rb +36 -0
- data/lib/rubocop/cop/gusto/paperclip_or_attachable.rb +17 -0
- data/lib/rubocop/cop/gusto/perform_class_method.rb +73 -0
- data/lib/rubocop/cop/gusto/polymorphic_type_validation.rb +89 -0
- data/lib/rubocop/cop/gusto/prefer_process_last_status.rb +35 -0
- data/lib/rubocop/cop/gusto/rabl_extends.rb +43 -0
- data/lib/rubocop/cop/gusto/rails_env.rb +72 -0
- data/lib/rubocop/cop/gusto/rake_constants.rb +68 -0
- data/lib/rubocop/cop/gusto/regexp_bypass.rb +90 -0
- data/lib/rubocop/cop/gusto/sidekiq_params.rb +21 -0
- data/lib/rubocop/cop/gusto/toplevel_constants.rb +55 -0
- data/lib/rubocop/cop/gusto/use_paint_not_colorize.rb +240 -0
- data/lib/rubocop/cop/gusto/vcr_recordings.rb +49 -0
- data/lib/rubocop/cop/internal_affairs/assignment_first.rb +56 -0
- data/lib/rubocop/cop/internal_affairs/require_restrict_on_send.rb +62 -0
- data/lib/rubocop/gusto/cli.rb +22 -0
- data/lib/rubocop/gusto/config_yml.rb +135 -0
- data/lib/rubocop/gusto/init.rb +59 -0
- data/lib/rubocop/gusto/plugin.rb +29 -0
- data/lib/rubocop/gusto/templates/rubocop.yml +25 -0
- data/lib/rubocop/gusto/version.rb +7 -0
- data/lib/rubocop/gusto.rb +9 -0
- data/lib/rubocop-gusto.rb +13 -0
- metadata +178 -0
data/config/rails.yml
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
#
|
2
|
+
# This file should be inherited alongside default.yml for Rails projects
|
3
|
+
#
|
4
|
+
# After you add a rule, sort this file with `bundle exec rubocop-gusto sort config/rails.yml`
|
5
|
+
|
6
|
+
plugins:
|
7
|
+
- rubocop-rails
|
8
|
+
|
9
|
+
AllCops:
|
10
|
+
TargetRubyVersion: <%= RbConfig::CONFIG['RUBY_API_VERSION'] %>
|
11
|
+
MaxFilesInCache: 100000
|
12
|
+
Exclude:
|
13
|
+
- './.[!.]*' # ignore all dotfiles
|
14
|
+
- './.[!.]*/**/*' # ignore all dot directories
|
15
|
+
- 'config/boot.rb'
|
16
|
+
- 'db/**/*schema.rb'
|
17
|
+
- 'db/seeds{.rb,/**/*}'
|
18
|
+
|
19
|
+
Rails:
|
20
|
+
Enabled: true
|
21
|
+
|
22
|
+
Rails/ActiveRecordAliases:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Rails/ActiveRecordOverride:
|
26
|
+
Enabled: true
|
27
|
+
|
28
|
+
Rails/ApplicationRecord:
|
29
|
+
AutoCorrect: true
|
30
|
+
|
31
|
+
Rails/BulkChangeTable:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
Rails/Date:
|
35
|
+
AutoCorrect: false
|
36
|
+
Enabled: true
|
37
|
+
|
38
|
+
Rails/Delegate:
|
39
|
+
# Incompatible with Sorbet
|
40
|
+
Enabled: false
|
41
|
+
EnforceForPrefixed: false
|
42
|
+
|
43
|
+
Rails/DynamicFindBy:
|
44
|
+
AllowedMethods:
|
45
|
+
- find_by_sql
|
46
|
+
- find_by_uuid_or_id
|
47
|
+
- find_by_uuid_or_id!
|
48
|
+
|
49
|
+
Rails/EagerEvaluationLogMessage:
|
50
|
+
Enabled: true
|
51
|
+
AutoCorrect: true
|
52
|
+
|
53
|
+
Rails/EnumHash:
|
54
|
+
Include:
|
55
|
+
- '**/app/models/**/*'
|
56
|
+
|
57
|
+
Rails/Exit:
|
58
|
+
Include:
|
59
|
+
- app/**/*
|
60
|
+
- config/**/*
|
61
|
+
- lib/**/*
|
62
|
+
- packs/**/{app,lib}/**/*
|
63
|
+
|
64
|
+
Rails/FindEach:
|
65
|
+
Include:
|
66
|
+
- '**/app/models/**/*'
|
67
|
+
|
68
|
+
Rails/HasAndBelongsToMany:
|
69
|
+
Include:
|
70
|
+
- '**/app/models/**/*'
|
71
|
+
|
72
|
+
Rails/HasManyOrHasOneDependent:
|
73
|
+
Enabled: false
|
74
|
+
|
75
|
+
Rails/HelperInstanceVariable:
|
76
|
+
Include:
|
77
|
+
- '**/app/helpers/**/*'
|
78
|
+
|
79
|
+
Rails/HttpPositionalArguments:
|
80
|
+
AutoCorrect: true
|
81
|
+
Include:
|
82
|
+
- packs/**/spec/{controllers,fixture_generators}/**/*
|
83
|
+
- spec/{controllers,fixture_generators}/**/*
|
84
|
+
|
85
|
+
Rails/InverseOf:
|
86
|
+
Include:
|
87
|
+
- '**/app/models/**/*'
|
88
|
+
|
89
|
+
Rails/LexicallyScopedActionFilter:
|
90
|
+
Include:
|
91
|
+
- '**/app/controllers/**/*'
|
92
|
+
- '**/app/mailers/**/*'
|
93
|
+
|
94
|
+
Rails/NotNullColumn:
|
95
|
+
Enabled: false
|
96
|
+
|
97
|
+
Rails/ReadWriteAttribute:
|
98
|
+
Enabled: false
|
99
|
+
|
100
|
+
Rails/SaveBang:
|
101
|
+
SafeAutoCorrect: false
|
102
|
+
|
103
|
+
Rails/SkipsModelValidations:
|
104
|
+
Enabled: true
|
105
|
+
|
106
|
+
Rails/TimeZone:
|
107
|
+
Enabled: true
|
108
|
+
|
109
|
+
Rails/UniqueValidationWithoutIndex:
|
110
|
+
Enabled: true
|
111
|
+
|
112
|
+
Rails/UnknownEnv:
|
113
|
+
Environments:
|
114
|
+
- demo
|
115
|
+
- development
|
116
|
+
- production
|
117
|
+
- staging
|
118
|
+
- test
|
119
|
+
|
120
|
+
Rails/Validation:
|
121
|
+
Include:
|
122
|
+
- '**/models/**/*'
|
data/exe/gusto-rubocop
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
# typed: strict
|
4
|
+
|
5
|
+
# This file is here to conform to our gusto-* convention.
|
6
|
+
# rubocop gems have their own naming convention so we're doing both.
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
9
|
+
|
10
|
+
require 'rubocop/gusto/cli'
|
11
|
+
|
12
|
+
RuboCop::Gusto::Cli.start(ARGV)
|
data/exe/rubocop-gusto
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
# Do not use Bootsnap to load files. Use `require` instead.
|
7
|
+
class BootsnapLoadFile < Base
|
8
|
+
PROHIBITED_CONSTANTS = Set[:YAML, :JSON].freeze
|
9
|
+
RESTRICT_ON_SEND = %i(load).freeze
|
10
|
+
|
11
|
+
# @!method yaml_or_json_load(node)
|
12
|
+
def_node_matcher :yaml_or_json_load, '(send $(const nil? PROHIBITED_CONSTANTS) :load ...)'
|
13
|
+
|
14
|
+
# @!method file_read(node)
|
15
|
+
def_node_matcher :file_read, '(send (const nil? :File) :read $_)'
|
16
|
+
|
17
|
+
# @!method load_inside_file_open(node)
|
18
|
+
def_node_matcher :load_inside_file_open, <<~PATTERN
|
19
|
+
(block
|
20
|
+
(send
|
21
|
+
(const nil? :File) :open
|
22
|
+
$(str _))
|
23
|
+
(args
|
24
|
+
(arg _file))
|
25
|
+
(send
|
26
|
+
$(const nil? :YAML) :load
|
27
|
+
(lvar _file))
|
28
|
+
)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def on_block(node)
|
32
|
+
load_inside_file_open(node) do |file_path_node, constant_node|
|
33
|
+
add_offense(node, message: "Use #{constant_node.source}.load_file(#{file_path_node.source}) to improve load time with bootsnap")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
alias_method :on_numblock, :on_block
|
37
|
+
|
38
|
+
def on_send(node)
|
39
|
+
yaml_or_json_load(node) do |constant_node|
|
40
|
+
on_load(node, constant_node)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Look for File.read as the first argument
|
47
|
+
def on_load(node, constant_node)
|
48
|
+
return unless node.first_argument
|
49
|
+
|
50
|
+
file_read(node.first_argument) do |read_file_node|
|
51
|
+
add_offense(node, message: "Use #{constant_node.source}.load_file(#{read_file_node.source}) to improve load time with bootsnap")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
class DatadogConstant < Base
|
7
|
+
MSG = 'Do not call Datadog directly, use an appropriate wrapper library.'
|
8
|
+
NAMESPACE = 'Datadog'
|
9
|
+
|
10
|
+
def on_const(node)
|
11
|
+
add_offense(node) if node.const_name == NAMESPACE
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
class ExecuteMigration < Base
|
7
|
+
MSG = 'Do not use `execute` to run raw SQL in a migration. Run the query from a backfill rake task or pass the SQL options to the `add_column`/`change_column` method.'
|
8
|
+
RESTRICT_ON_SEND = [:execute].freeze
|
9
|
+
|
10
|
+
def on_send(node)
|
11
|
+
add_offense(node)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
class FactoryClassesOrModules < Base
|
7
|
+
MSG = 'Do not define modules or classes in factory directories - they break reloading'
|
8
|
+
|
9
|
+
def on_class(node)
|
10
|
+
add_offense(node)
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_module(node)
|
14
|
+
add_offense(node)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
# Checks for the use of `min` or `max` with a proc. Corrects to `min_by` or `max_by`.
|
7
|
+
#
|
8
|
+
# @safety This cop is unsafe because it will change the behavior of the code.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# arr = [[3, 3, 3], [2, 2], [1]]
|
13
|
+
# arr.min(&:count)
|
14
|
+
# => [3, 3, 3] - oh no how did this happen?
|
15
|
+
# arr = [[2,2],[1,1],[3,3,3]]
|
16
|
+
# arr.min &:first # => TypeError: no implicit conversion of Array into Integer
|
17
|
+
# arr = [[1, 1], [3, 3], [2, 2]]
|
18
|
+
# arr.max { |pair| pair.first } # => [2, 2] (semantically incorrect)
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# arr = [[2,2],[1],[3,3,3]]
|
22
|
+
# arr.min_by &:first # => [1]
|
23
|
+
# arr = [[1, 1], [3, 3], [2, 2]]
|
24
|
+
# arr.max_by { |pair| pair.first } # => [3, 3]
|
25
|
+
#
|
26
|
+
class MinByMaxBy < Base
|
27
|
+
extend AutoCorrector
|
28
|
+
|
29
|
+
MSG = 'Use `%{method}_by` instead of `%{method}` with a proc like `&:my_method_proc`. `%{method}` expects Comparable elements.'
|
30
|
+
RESTRICT_ON_SEND = %i(min max).freeze
|
31
|
+
|
32
|
+
def on_send(node)
|
33
|
+
return unless node.arguments?
|
34
|
+
return unless node.first_argument.block_pass_type?
|
35
|
+
|
36
|
+
method_name = node.method_name
|
37
|
+
add_offense(node, message: format(MSG, method: method_name)) do |corrector|
|
38
|
+
corrector.replace(node, node.source.sub(method_name.to_s, "#{method_name}_by"))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
alias_method :on_csend, :on_send
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
# Checks and discourages the use of metaprogramming techniques that make code harder to
|
7
|
+
# understand, debug, and maintain.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad - using define_method
|
12
|
+
# define_method(:my_method) do |arg|
|
13
|
+
# puts arg
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good - using regular method definition
|
17
|
+
# def my_method(arg)
|
18
|
+
# puts arg
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # bad - using instance_eval
|
22
|
+
# object.instance_eval do
|
23
|
+
# def foo
|
24
|
+
# bar
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # good - defining methods on the class
|
29
|
+
# class MyClass
|
30
|
+
# def foo
|
31
|
+
# bar
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # bad - using method_missing
|
36
|
+
# def method_missing(method, *args)
|
37
|
+
# if method.to_s.start_with?('find_by_')
|
38
|
+
# # ...
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# # bad - using define_singleton_method
|
43
|
+
# object.define_singleton_method(:foo) { bar }
|
44
|
+
#
|
45
|
+
# # good - define class methods directly
|
46
|
+
# def self.foo
|
47
|
+
# bar
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# # bad - using class_eval
|
51
|
+
# MyClass.class_eval do
|
52
|
+
# def foo
|
53
|
+
# bar
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
class NoMetaprogramming < Base
|
58
|
+
RESTRICT_ON_SEND = %i(define_method instance_eval define_singleton_method class_eval).freeze
|
59
|
+
|
60
|
+
# @!method included_definition?(node)
|
61
|
+
def_node_matcher :included_definition?, <<~PATTERN
|
62
|
+
(defs self :included ...)
|
63
|
+
PATTERN
|
64
|
+
|
65
|
+
# @!method inherited_definition?(node)
|
66
|
+
def_node_matcher :inherited_definition?, <<~PATTERN
|
67
|
+
(defs self :inherited ...)
|
68
|
+
PATTERN
|
69
|
+
|
70
|
+
# @!method using_method_missing?(node)
|
71
|
+
def_node_matcher :using_method_missing?, <<~PATTERN
|
72
|
+
(def :method_missing ...)
|
73
|
+
PATTERN
|
74
|
+
|
75
|
+
# @!method using_define_method?(node)
|
76
|
+
def_node_matcher :using_define_method?, <<~PATTERN
|
77
|
+
(send _ :define_method ...)
|
78
|
+
PATTERN
|
79
|
+
|
80
|
+
# @!method using_instance_eval?(node)
|
81
|
+
def_node_matcher :using_instance_eval?, <<~PATTERN
|
82
|
+
(send _ :instance_eval ...)
|
83
|
+
PATTERN
|
84
|
+
|
85
|
+
# @!method using_class_eval?(node)
|
86
|
+
def_node_matcher :using_class_eval?, <<~PATTERN
|
87
|
+
(send _ :class_eval ...)
|
88
|
+
PATTERN
|
89
|
+
|
90
|
+
# @!method using_define_singleton_method_on_klass_instance?(node)
|
91
|
+
def_node_matcher :using_define_singleton_method_on_klass_instance?, <<~PATTERN
|
92
|
+
(send _ :define_singleton_method ...)
|
93
|
+
PATTERN
|
94
|
+
|
95
|
+
def on_defs(node)
|
96
|
+
included_definition?(node) do
|
97
|
+
add_offense(node, message: 'self.included modifies the behavior of classes at runtime. Please avoid using if possible.')
|
98
|
+
end
|
99
|
+
|
100
|
+
inherited_definition?(node) do
|
101
|
+
add_offense(node, message: 'self.inherited modifies the behavior of classes at runtime. Please avoid using if possible.')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def on_def(node)
|
106
|
+
using_method_missing?(node) do
|
107
|
+
add_offense(node, message: 'Please do not use method_missing. Instead, explicitly define the methods you expect to receive.')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def on_send(node)
|
112
|
+
using_define_method?(node) do
|
113
|
+
add_offense(node, message: 'Please do not define methods dynamically, instead define them using `def` and explicitly. This helps readability for both humans and machines.')
|
114
|
+
end
|
115
|
+
|
116
|
+
using_define_singleton_method_on_klass_instance?(node) do
|
117
|
+
add_offense(node, message: 'Please do not use define_singleton_method. Instead, define the method explicitly using `def self.my_method; end`')
|
118
|
+
end
|
119
|
+
|
120
|
+
using_instance_eval?(node) do
|
121
|
+
add_offense(node, message: 'Please do not use instance_eval to augment behavior onto an instance. Instead, define the method you want to use in the class definition.')
|
122
|
+
end
|
123
|
+
|
124
|
+
using_class_eval?(node) do
|
125
|
+
add_offense(node, message: 'Please do not use class_eval to augment behavior onto a class. Instead, define the method you want to use in the class definition.')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
# Checks for the presence of error message checking within rescue blocks.
|
7
|
+
#
|
8
|
+
# This is brittle and can break easily.
|
9
|
+
#
|
10
|
+
# NOTE: We submitted this upstream here: https://github.com/rubocop/rubocop/pull/13352
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# # bad
|
15
|
+
# begin
|
16
|
+
# something
|
17
|
+
# rescue => e
|
18
|
+
# if e.message.match?(/Duplicate entry/)
|
19
|
+
# handle_error
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # bad
|
24
|
+
# begin
|
25
|
+
# something
|
26
|
+
# rescue => e
|
27
|
+
# unless e.message.match?(/Duplicate entry/)
|
28
|
+
# handle_error
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # good
|
33
|
+
# begin
|
34
|
+
# something
|
35
|
+
# rescue ActiveRecord::RecordNotUnique => e
|
36
|
+
# handle_error
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
class NoRescueErrorMessageChecking < Base
|
40
|
+
MSG = 'Avoid checking error message while handling exceptions. This is brittle and can break easily.'
|
41
|
+
METHODS_TO_CHECK = %i(match? include? ==).to_set.freeze
|
42
|
+
|
43
|
+
def on_rescue(node)
|
44
|
+
node.resbody_branches.last.each_descendant(:if, :unless).each do |condition_node|
|
45
|
+
add_offense(condition_node) if message_check?(condition_node)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def message_check?(condition_node)
|
52
|
+
return unless condition_node.condition.send_type?
|
53
|
+
return unless condition_node.condition.receiver
|
54
|
+
return unless METHODS_TO_CHECK.include?(condition_node.condition.method_name)
|
55
|
+
|
56
|
+
if condition_node.condition.method?(:==)
|
57
|
+
(condition_node.condition.receiver.str_type? && condition_node.condition.first_argument.method?(:message)) ||
|
58
|
+
(condition_node.condition.receiver.send_type? && condition_node.condition.receiver.method?(:message))
|
59
|
+
else
|
60
|
+
condition_node.condition.receiver.type?(:send, :regexp, :array)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
# Do not call a private method via __send__
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# foo.__send__(:bar)
|
11
|
+
# __send__(:run_baz)
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# There's no better alternative, don't call private methods.
|
15
|
+
#
|
16
|
+
class NoSend < Base
|
17
|
+
MSG = 'Do not call a private method via `__send__`.'
|
18
|
+
RESTRICT_ON_SEND = %i(__send__).freeze
|
19
|
+
|
20
|
+
# @!method invoke_private_method_send?(node)
|
21
|
+
def_node_matcher :invoke_private_method_send?, <<~PATTERN
|
22
|
+
(call _ :__send__ (sym _))
|
23
|
+
PATTERN
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
invoke_private_method_send?(node) { add_offense(node) }
|
27
|
+
end
|
28
|
+
alias_method :on_csend, :on_send
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
# Identifies uses of `Object#in?`, which iterates over each
|
7
|
+
# item in a `Range` to see if a specified item is there. In contrast,
|
8
|
+
# `Range#cover?` simply compares the target item with the beginning and
|
9
|
+
# end points of the `Range`. In a great majority of cases, this is what
|
10
|
+
# is wanted.
|
11
|
+
#
|
12
|
+
# @safety
|
13
|
+
# This cop is unsafe. Here is an example of a case where `Range#cover?`
|
14
|
+
# may not provide the desired result:
|
15
|
+
#
|
16
|
+
# ('a'..'z').cover?('yellow') # => true
|
17
|
+
#
|
18
|
+
class ObjectIn < Base
|
19
|
+
MSG = 'Use `Range#cover?` instead of `Object#in?`.'
|
20
|
+
RESTRICT_ON_SEND = [:in?].freeze
|
21
|
+
|
22
|
+
# @!method object_in(node)
|
23
|
+
def_node_matcher :object_in, <<-PATTERN
|
24
|
+
(call _ :in? {range (begin range)})
|
25
|
+
PATTERN
|
26
|
+
|
27
|
+
def on_send(node)
|
28
|
+
return unless object_in(node)
|
29
|
+
|
30
|
+
add_offense(node)
|
31
|
+
end
|
32
|
+
alias_method :on_csend, :on_send
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# typed: ignore
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RuboCop
|
5
|
+
module Cop
|
6
|
+
module Gusto
|
7
|
+
class PaperclipOrAttachable < Base
|
8
|
+
MSG = 'No more new paperclip or Attachable are allowed. New attachments should use ActiveStorage instead'
|
9
|
+
RESTRICT_ON_SEND = %i(has_attached_file has_pdf_attachment has_attachment).freeze
|
10
|
+
|
11
|
+
def on_send(node)
|
12
|
+
add_offense(node)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gusto
|
6
|
+
# Checks for a defined `.perform` class method in Sidekiq workers. These
|
7
|
+
# are most likely intended to have been instance methods.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# class MyWorker
|
12
|
+
# include Sidekiq::Worker
|
13
|
+
#
|
14
|
+
# def self.perform
|
15
|
+
# # ...
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# class MyWorker
|
21
|
+
# include Sidekiq::Worker
|
22
|
+
#
|
23
|
+
# def perform
|
24
|
+
# # ...
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
class PerformClassMethod < Base
|
29
|
+
MSG = 'Class-level `perform` method is being defined. Did you mean to use an instance method?'
|
30
|
+
WORKER_FALLBACK = %w(Sidekiq::Worker).freeze
|
31
|
+
WORKER_MODULES = 'WorkerModules'
|
32
|
+
|
33
|
+
def on_def(node)
|
34
|
+
return unless node.method?(:perform)
|
35
|
+
return unless (method_type = perform_class_method_type(node))
|
36
|
+
return unless is_sidekiq_worker?(node, method_type)
|
37
|
+
|
38
|
+
add_offense(node)
|
39
|
+
end
|
40
|
+
alias_method :on_defs, :on_def
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def perform_class_method_type(node)
|
45
|
+
if node.receiver&.self_type?
|
46
|
+
:self
|
47
|
+
elsif node.parent.sclass_type?
|
48
|
+
:sclass
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def is_sidekiq_worker?(search_node, method_type)
|
53
|
+
search_node = search_node.parent if method_type == :sclass
|
54
|
+
search_node.parent.children.any? do |sibling|
|
55
|
+
next if sibling.nil?
|
56
|
+
next unless is_include?(sibling)
|
57
|
+
next unless sibling.first_argument.const_type?
|
58
|
+
|
59
|
+
worker_modules.include?(sibling.first_argument.const_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def is_include?(node)
|
64
|
+
node.send_type? && node.method?(:include)
|
65
|
+
end
|
66
|
+
|
67
|
+
def worker_modules
|
68
|
+
@worker_modules ||= cop_config.fetch(WORKER_MODULES, WORKER_FALLBACK)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|