rubocop-rails 2.12.2 → 2.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/config/default.yml +43 -8
  4. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +34 -0
  5. data/lib/rubocop/cop/rails/active_record_aliases.rb +6 -2
  6. data/lib/rubocop/cop/rails/application_controller.rb +5 -1
  7. data/lib/rubocop/cop/rails/application_job.rb +5 -1
  8. data/lib/rubocop/cop/rails/application_mailer.rb +5 -1
  9. data/lib/rubocop/cop/rails/application_record.rb +6 -1
  10. data/lib/rubocop/cop/rails/arel_star.rb +6 -0
  11. data/lib/rubocop/cop/rails/blank.rb +5 -4
  12. data/lib/rubocop/cop/rails/compact_blank.rb +98 -0
  13. data/lib/rubocop/cop/rails/content_tag.rb +15 -8
  14. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -7
  15. data/lib/rubocop/cop/rails/duration_arithmetic.rb +97 -0
  16. data/lib/rubocop/cop/rails/dynamic_find_by.rb +4 -0
  17. data/lib/rubocop/cop/rails/find_each.rb +13 -0
  18. data/lib/rubocop/cop/rails/http_positional_arguments.rb +1 -1
  19. data/lib/rubocop/cop/rails/index_by.rb +6 -6
  20. data/lib/rubocop/cop/rails/index_with.rb +6 -6
  21. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +8 -7
  22. data/lib/rubocop/cop/rails/mailer_name.rb +4 -0
  23. data/lib/rubocop/cop/rails/negate_include.rb +3 -2
  24. data/lib/rubocop/cop/rails/output.rb +4 -0
  25. data/lib/rubocop/cop/rails/pick.rb +7 -0
  26. data/lib/rubocop/cop/rails/pluck_id.rb +3 -0
  27. data/lib/rubocop/cop/rails/pluck_in_where.rb +7 -6
  28. data/lib/rubocop/cop/rails/rake_environment.rb +5 -0
  29. data/lib/rubocop/cop/rails/read_write_attribute.rb +21 -0
  30. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +256 -0
  31. data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -2
  32. data/lib/rubocop/cop/rails/relative_date_constant.rb +4 -1
  33. data/lib/rubocop/cop/rails/reversible_migration.rb +11 -3
  34. data/lib/rubocop/cop/rails/root_join_chain.rb +72 -0
  35. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +12 -3
  36. data/lib/rubocop/cop/rails/save_bang.rb +19 -0
  37. data/lib/rubocop/cop/rails/schema_comment.rb +104 -0
  38. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +4 -2
  39. data/lib/rubocop/cop/rails/time_zone.rb +3 -0
  40. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +29 -35
  41. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +1 -1
  42. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +2 -0
  43. data/lib/rubocop/cop/rails/where_equals.rb +4 -0
  44. data/lib/rubocop/cop/rails/where_exists.rb +9 -8
  45. data/lib/rubocop/cop/rails_cops.rb +6 -0
  46. data/lib/rubocop/rails/version.rb +1 -1
  47. metadata +11 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33fc3c9951987b64d83ea0f53cbc3609a41d4893f20b4a58632839452d706765
4
- data.tar.gz: 4ba428e15dbd23dcfc9c3a93fe5a0783eae791313d96ddbd1829e997f35f2e2d
3
+ metadata.gz: 624c6bfb8dc9c5db0f6dd6bec835bd71e5e5a7aadc298342c15faae187226079
4
+ data.tar.gz: 4354d39a8367f0e4cb46b5a890cdd0836bfff6d838b5ae320b3df3a480f529af
5
5
  SHA512:
6
- metadata.gz: a84e17fd45978e65df389ef959b99d917c97407dd44138e8bc6671303ac75f81d3c507f6336b984c7da259847e050fd347754c2f88caa31f5585f52fe2ac1821
7
- data.tar.gz: 1c12a4b21af9b81266ee0adb96b369548f190369f01f28ea42b67e2be916c5a3e5a4817522cca19e7c0ba1c513ef87f93914b46097500456e827cae1ccda1303
6
+ metadata.gz: 19863e8b5b71b4403d66b46e73ed41ceb1953d2a68996fbcc800ab4c06fcf4dfebadf3907f62d2c575b002223a95fa67fce2412c01fa42b6234848c2791b37e9
7
+ data.tar.gz: 1e3d14e3240f7fdb9182529865ee8257cf19368bac19c33abfba44b0a3b63c54e64457f73725e6c629a5846ea402a96e650f7d6ce0e1943386c857d6a8642d7b
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-21 Bozhidar Batsov
1
+ Copyright (c) 2012-22 Bozhidar Batsov
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/config/default.yml CHANGED
@@ -9,7 +9,7 @@ AllCops:
9
9
  - bin/*
10
10
  - db/schema.rb
11
11
  # What version of Rails is the inspected code using? If a value is specified
12
- # for TargetRailsVersion then it is used. Acceptable values are specificed
12
+ # for TargetRailsVersion then it is used. Acceptable values are specified
13
13
  # as a float (i.e. 5.1); the patch version of Rails should not be included.
14
14
  # If TargetRailsVersion is not set, RuboCop will parse the Gemfile.lock or
15
15
  # gems.locked file to find the version of Rails that has been bound to the
@@ -180,6 +180,12 @@ Rails/BulkChangeTable:
180
180
  Include:
181
181
  - db/migrate/*.rb
182
182
 
183
+ Rails/CompactBlank:
184
+ Description: 'Checks if collection can be blank-compacted with `compact_blank`.'
185
+ Enabled: pending
186
+ Safe: false
187
+ VersionAdded: '2.13'
188
+
183
189
  Rails/ContentTag:
184
190
  Description: 'Use `tag.something` instead of `tag(:something)`.'
185
191
  Reference:
@@ -189,6 +195,9 @@ Rails/ContentTag:
189
195
  Enabled: true
190
196
  VersionAdded: '2.6'
191
197
  VersionChanged: '2.12'
198
+ # This `Exclude` config prevents false positives for `tag` calls to `has_one: tag`. No helpers are used in normal models.
199
+ Exclude:
200
+ - app/models/**/*.rb
192
201
 
193
202
  Rails/CreateTableWithTimestamps:
194
203
  Description: >-
@@ -198,6 +207,10 @@ Rails/CreateTableWithTimestamps:
198
207
  VersionAdded: '0.52'
199
208
  Include:
200
209
  - db/migrate/*.rb
210
+ Exclude:
211
+ # Respect the `active_storage_variant_records` table of `*_create_active_storage_tables.active_storage.rb`
212
+ # auto-generated by `bin/rails active_storage:install` even if `created_at` is not specified.
213
+ - db/migrate/*_create_active_storage_tables.active_storage.rb
201
214
 
202
215
  Rails/Date:
203
216
  Description: >-
@@ -238,6 +251,12 @@ Rails/DelegateAllowBlank:
238
251
  Enabled: true
239
252
  VersionAdded: '0.44'
240
253
 
254
+ Rails/DurationArithmetic:
255
+ Description: 'Do not use duration as arithmetic operand with `Time.current`.'
256
+ StyleGuide: 'https://rails.rubystyle.guide#duration-arithmetic'
257
+ Enabled: pending
258
+ VersionAdded: '2.13'
259
+
241
260
  Rails/DynamicFindBy:
242
261
  Description: 'Use `find_by` instead of dynamic `find_by_*`.'
243
262
  StyleGuide: 'https://rails.rubystyle.guide#find_by'
@@ -605,6 +624,11 @@ Rails/RedundantForeignKey:
605
624
  Enabled: true
606
625
  VersionAdded: '2.6'
607
626
 
627
+ Rails/RedundantPresenceValidationOnBelongsTo:
628
+ Description: 'Checks for redundant presence validation on belongs_to association.'
629
+ Enabled: pending
630
+ VersionAdded: '2.13'
631
+
608
632
  Rails/RedundantReceiverInWithOptions:
609
633
  Description: 'Checks for redundant receiver in `with_options`.'
610
634
  Enabled: true
@@ -639,9 +663,9 @@ Rails/RefuteMethods:
639
663
  Rails/RelativeDateConstant:
640
664
  Description: 'Do not assign relative date to constants.'
641
665
  Enabled: true
666
+ SafeAutoCorrect: false
642
667
  VersionAdded: '0.48'
643
- VersionChanged: '0.59'
644
- AutoCorrect: false
668
+ VersionChanged: '2.13'
645
669
 
646
670
  Rails/RenderInline:
647
671
  Description: 'Prefer using a template over inline rendering.'
@@ -688,6 +712,11 @@ Rails/ReversibleMigrationMethodDefinition:
688
712
  Include:
689
713
  - db/migrate/*.rb
690
714
 
715
+ Rails/RootJoinChain:
716
+ Description: 'Use a single `#join` instead of chaining on `Rails.root` or `Rails.public_path`.'
717
+ Enabled: pending
718
+ VersionAdded: '2.13'
719
+
691
720
  Rails/SafeNavigation:
692
721
  Description: "Use Ruby's safe navigation operator (`&.`) instead of `try!`."
693
722
  Enabled: true
@@ -720,6 +749,13 @@ Rails/SaveBang:
720
749
  AllowedReceivers: []
721
750
  SafeAutoCorrect: false
722
751
 
752
+ Rails/SchemaComment:
753
+ Description: >-
754
+ This cop enforces the use of the `comment` option when adding a new table or column
755
+ to the database during a migration.
756
+ Enabled: false
757
+ VersionAdded: '2.13'
758
+
723
759
  Rails/ScopeArgs:
724
760
  Description: 'Checks the arguments of ActiveRecord scopes.'
725
761
  Enabled: true
@@ -782,9 +818,9 @@ Rails/TimeZone:
782
818
  StyleGuide: 'https://rails.rubystyle.guide#time'
783
819
  Reference: 'http://danilenko.org/2012/7/6/rails_timezones'
784
820
  Enabled: true
785
- Safe: false
821
+ SafeAutoCorrect: false
786
822
  VersionAdded: '0.30'
787
- VersionChanged: '2.10'
823
+ VersionChanged: '2.13'
788
824
  # The value `strict` means that `Time` should be used with `zone`.
789
825
  # The value `flexible` allows usage of `in_time_zone` instead of `zone`.
790
826
  EnforcedStyle: flexible
@@ -807,16 +843,15 @@ Rails/UniqBeforePluck:
807
843
  Description: 'Prefer the use of uniq or distinct before pluck.'
808
844
  Enabled: true
809
845
  VersionAdded: '0.40'
810
- VersionChanged: '2.8'
846
+ VersionChanged: '2.13'
811
847
  EnforcedStyle: conservative
812
848
  SupportedStyles:
813
849
  - conservative
814
850
  - aggressive
815
851
  SafeAutoCorrect: false
816
- AutoCorrect: false
817
852
 
818
853
  Rails/UniqueValidationWithoutIndex:
819
- Description: 'Uniqueness validation should be with a unique index.'
854
+ Description: 'Uniqueness validation should have a unique index on the database column.'
820
855
  Enabled: true
821
856
  VersionAdded: '2.5'
822
857
  Include:
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # A mixin to extend cops for Active Record features
6
+ module ActiveRecordMigrationsHelper
7
+ extend NodePattern::Macros
8
+
9
+ RAILS_ABSTRACT_SCHEMA_DEFINITIONS = %i[
10
+ bigint binary boolean date datetime decimal float integer json string
11
+ text time timestamp virtual
12
+ ].freeze
13
+ RAILS_ABSTRACT_SCHEMA_DEFINITIONS_HELPERS = %i[
14
+ column references belongs_to primary_key numeric
15
+ ].freeze
16
+ POSTGRES_SCHEMA_DEFINITIONS = %i[
17
+ bigserial bit bit_varying cidr citext daterange hstore inet interval
18
+ int4range int8range jsonb ltree macaddr money numrange oid point line
19
+ lseg box path polygon circle serial tsrange tstzrange tsvector uuid xml
20
+ ].freeze
21
+ MYSQL_SCHEMA_DEFINITIONS = %i[
22
+ blob tinyblob mediumblob longblob tinytext mediumtext longtext
23
+ unsigned_integer unsigned_bigint unsigned_float unsigned_decimal
24
+ ].freeze
25
+
26
+ def_node_matcher :create_table_with_block?, <<~PATTERN
27
+ (block
28
+ (send nil? :create_table ...)
29
+ (args (arg _var))
30
+ _)
31
+ PATTERN
32
+ end
33
+ end
34
+ end
@@ -6,12 +6,16 @@ module RuboCop
6
6
  # Checks that ActiveRecord aliases are not used. The direct method names
7
7
  # are more clear and easier to read.
8
8
  #
9
+ # @safety
10
+ # This cop is unsafe because custom `update_attributes` method call was changed to
11
+ # `update` but the method name remained same in the method definition.
12
+ #
9
13
  # @example
10
14
  # #bad
11
- # Book.update_attributes!(author: 'Alice')
15
+ # book.update_attributes!(author: 'Alice')
12
16
  #
13
17
  # #good
14
- # Book.update!(author: 'Alice')
18
+ # book.update!(author: 'Alice')
15
19
  class ActiveRecordAliases < Base
16
20
  extend AutoCorrector
17
21
 
@@ -3,7 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that controllers subclass ApplicationController.
6
+ # This cop checks that controllers subclass `ApplicationController`.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because it may let the logic from `ApplicationController`
10
+ # sneak into a controller that is not purposed to inherit logic common among other controllers.
7
11
  #
8
12
  # @example
9
13
  #
@@ -3,7 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that jobs subclass ApplicationJob with Rails 5.0.
6
+ # This cop checks that jobs subclass `ApplicationJob` with Rails 5.0.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because it may let the logic from `ApplicationJob`
10
+ # sneak into a job that is not purposed to inherit logic common among other jobs.
7
11
  #
8
12
  # @example
9
13
  #
@@ -3,7 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that mailers subclass ApplicationMailer with Rails 5.0.
6
+ # This cop checks that mailers subclass `ApplicationMailer` with Rails 5.0.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because it may let the logic from `ApplicationMailer`
10
+ # sneak into a mailer that is not purposed to inherit logic common among other mailers.
7
11
  #
8
12
  # @example
9
13
  #
@@ -3,7 +3,12 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that models subclass ApplicationRecord with Rails 5.0.
6
+ # This cop checks that models subclass `ApplicationRecord` with Rails 5.0.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because it may let the logic from `ApplicationRecord`
10
+ # sneak into an Active Record model that is not purposed to inherit logic common among other
11
+ # Active Record models.
7
12
  #
8
13
  # @example
9
14
  #
@@ -10,6 +10,12 @@ module RuboCop
10
10
  # database to look for a column named <tt>`*`</tt> (or `"*"`) as opposed
11
11
  # to expanding the column list as one would likely expect.
12
12
  #
13
+ # @safety
14
+ # This cop's autocorrection is unsafe because it turns a quoted `*` into
15
+ # an SQL `*`, unquoted. `*` is a valid column name in certain databases
16
+ # supported by Rails, and even though it is usually a mistake,
17
+ # it might denote legitimate access to a column named `*`.
18
+ #
13
19
  # @example
14
20
  # # bad
15
21
  # MyTable.arel_table["*"]
@@ -6,15 +6,16 @@ module RuboCop
6
6
  # This cop checks for code that can be written with simpler conditionals
7
7
  # using `Object#blank?` defined by Active Support.
8
8
  #
9
- # This cop is marked as unsafe auto-correction, because `' '.empty?` returns false,
10
- # but `' '.blank?` returns true. Therefore, auto-correction is not compatible
11
- # if the receiver is a non-empty blank string, tab, or newline meta characters.
12
- #
13
9
  # Interaction with `Style/UnlessElse`:
14
10
  # The configuration of `NotPresent` will not produce an offense in the
15
11
  # context of `unless else` if `Style/UnlessElse` is inabled. This is
16
12
  # to prevent interference between the auto-correction of the two cops.
17
13
  #
14
+ # @safety
15
+ # This cop is unsafe auto-correction, because `' '.empty?` returns false,
16
+ # but `' '.blank?` returns true. Therefore, auto-correction is not compatible
17
+ # if the receiver is a non-empty blank string, tab, or newline meta characters.
18
+ #
18
19
  # @example NilOrEmpty: true (default)
19
20
  # # Converts usages of `nil? || empty?` to `blank?`
20
21
  #
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks if collection can be blank-compacted with `compact_blank`.
7
+ #
8
+ # @safety
9
+ # It is unsafe by default because false positives may occur in the
10
+ # blank check of block arguments to the receiver object.
11
+ #
12
+ # For example, `[[1, 2], [3, nil]].reject { |first, second| second.blank? }` and
13
+ # `[[1, 2], [3, nil]].compact_blank` are not compatible. The same is true for `empty?`.
14
+ # This will work fine when the receiver is a hash object.
15
+ #
16
+ # @example
17
+ #
18
+ # # bad
19
+ # collection.reject(&:blank?)
20
+ # collection.reject(&:empty?)
21
+ # collection.reject { |_k, v| v.blank? }
22
+ # collection.reject { |_k, v| v.empty? }
23
+ #
24
+ # # good
25
+ # collection.compact_blank
26
+ #
27
+ # # bad
28
+ # collection.reject!(&:blank?)
29
+ # collection.reject!(&:empty?)
30
+ # collection.reject! { |_k, v| v.blank? }
31
+ # collection.reject! { |_k, v| v.empty? }
32
+ #
33
+ # # good
34
+ # collection.compact_blank!
35
+ #
36
+ class CompactBlank < Base
37
+ include RangeHelp
38
+ extend AutoCorrector
39
+ extend TargetRailsVersion
40
+
41
+ MSG = 'Use `%<preferred_method>s` instead.'
42
+ RESTRICT_ON_SEND = %i[reject reject!].freeze
43
+
44
+ minimum_target_rails_version 6.1
45
+
46
+ def_node_matcher :reject_with_block?, <<~PATTERN
47
+ (block
48
+ (send _ {:reject :reject!})
49
+ $(args ...)
50
+ (send
51
+ $(lvar _) {:blank? :empty?}))
52
+ PATTERN
53
+
54
+ def_node_matcher :reject_with_block_pass?, <<~PATTERN
55
+ (send _ {:reject :reject!}
56
+ (block_pass
57
+ (sym {:blank? :empty?})))
58
+ PATTERN
59
+
60
+ def on_send(node)
61
+ return unless bad_method?(node)
62
+
63
+ range = offense_range(node)
64
+ preferred_method = preferred_method(node)
65
+ add_offense(range, message: format(MSG, preferred_method: preferred_method)) do |corrector|
66
+ corrector.replace(range, preferred_method)
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def bad_method?(node)
73
+ return true if reject_with_block_pass?(node)
74
+
75
+ if (arguments, receiver_in_block = reject_with_block?(node.parent))
76
+ return arguments.length == 1 || use_hash_value_block_argument?(arguments, receiver_in_block)
77
+ end
78
+
79
+ false
80
+ end
81
+
82
+ def use_hash_value_block_argument?(arguments, receiver_in_block)
83
+ arguments.length == 2 && arguments[1].source == receiver_in_block.source
84
+ end
85
+
86
+ def offense_range(node)
87
+ end_pos = node.parent&.block_type? ? node.parent.loc.expression.end_pos : node.loc.expression.end_pos
88
+
89
+ range_between(node.loc.selector.begin_pos, end_pos)
90
+ end
91
+
92
+ def preferred_method(node)
93
+ node.method?(:reject) ? 'compact_blank' : 'compact_blank!'
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -6,8 +6,8 @@ module RuboCop
6
6
  # This cop checks legacy syntax usage of `tag`
7
7
  #
8
8
  # NOTE: Allow `tag` when the first argument is a variable because
9
- # `tag(name)` is simpler rather than `tag.public_send(name)`.
10
- # And this cop will be renamed to something like `LegacyTag` in the future. (e.g. RuboCop Rails 2.0)
9
+ # `tag(name)` is simpler rather than `tag.public_send(name)`.
10
+ # And this cop will be renamed to something like `LegacyTag` in the future. (e.g. RuboCop Rails 2.0)
11
11
  #
12
12
  # @example
13
13
  # # bad
@@ -33,6 +33,9 @@ module RuboCop
33
33
  end
34
34
 
35
35
  def on_send(node)
36
+ return unless node.receiver.nil?
37
+ return if node.arguments.count >= 3
38
+
36
39
  first_argument = node.first_argument
37
40
  return if !first_argument ||
38
41
  allowed_argument?(first_argument) ||
@@ -41,12 +44,7 @@ module RuboCop
41
44
  preferred_method = node.first_argument.value.to_s.underscore
42
45
  message = format(MSG, preferred_method: preferred_method, current_argument: first_argument.source)
43
46
 
44
- add_offense(node, message: message) do |corrector|
45
- autocorrect(corrector, node, preferred_method)
46
-
47
- @corrected_nodes ||= Set.new.compare_by_identity
48
- @corrected_nodes.add(node)
49
- end
47
+ register_offense(node, message, preferred_method)
50
48
  end
51
49
 
52
50
  private
@@ -63,6 +61,15 @@ module RuboCop
63
61
  allowed_name?(argument)
64
62
  end
65
63
 
64
+ def register_offense(node, message, preferred_method)
65
+ add_offense(node, message: message) do |corrector|
66
+ autocorrect(corrector, node, preferred_method)
67
+
68
+ @corrected_nodes ||= Set.new.compare_by_identity
69
+ @corrected_nodes.add(node)
70
+ end
71
+ end
72
+
66
73
  def autocorrect(corrector, node, preferred_method)
67
74
  range = correction_range(node)
68
75
 
@@ -41,16 +41,11 @@ module RuboCop
41
41
  # t.datetime :updated_at, default: -> { 'CURRENT_TIMESTAMP' }
42
42
  # end
43
43
  class CreateTableWithTimestamps < Base
44
+ include ActiveRecordMigrationsHelper
45
+
44
46
  MSG = 'Add timestamps when creating a new table.'
45
47
  RESTRICT_ON_SEND = %i[create_table].freeze
46
48
 
47
- def_node_matcher :create_table_with_block?, <<~PATTERN
48
- (block
49
- (send nil? :create_table ...)
50
- (args (arg _var))
51
- _)
52
- PATTERN
53
-
54
49
  def_node_matcher :create_table_with_timestamps_proc?, <<~PATTERN
55
50
  (send nil? :create_table (sym _) ... (block-pass (sym :timestamps)))
56
51
  PATTERN
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks if a duration is added to or subtracted from `Time.current`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # Time.current - 1.minute
11
+ # Time.current + 2.days
12
+ #
13
+ # # good - using relative would make it harder to express and read
14
+ # Date.yesterday + 3.days
15
+ # created_at - 1.minute
16
+ # 3.days - 1.hour
17
+ #
18
+ # # good
19
+ # 1.minute.ago
20
+ # 2.days.from_now
21
+ class DurationArithmetic < Base
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Do not add or subtract duration.'
25
+
26
+ RESTRICT_ON_SEND = %i[+ -].freeze
27
+
28
+ DURATIONS = Set[:second, :seconds, :minute, :minutes, :hour, :hours,
29
+ :day, :days, :week, :weeks, :fortnight, :fortnights]
30
+
31
+ # @!method duration_arithmetic_argument?(node)
32
+ # Match duration subtraction or addition with current time.
33
+ #
34
+ # @example source that matches
35
+ # Time.current - 1.hour
36
+ #
37
+ # @example source that matches
38
+ # ::Time.zone.now + 1.hour
39
+ #
40
+ # @param node [RuboCop::AST::Node]
41
+ # @yield operator and duration
42
+ def_node_matcher :duration_arithmetic_argument?, <<~PATTERN
43
+ (send #time_current? ${ :+ :- } $#duration?)
44
+ PATTERN
45
+
46
+ # @!method duration?(node)
47
+ # Match a literal Duration
48
+ #
49
+ # @example source that matches
50
+ # 1.hour
51
+ #
52
+ # @example source that matches
53
+ # 9.5.weeks
54
+ #
55
+ # @param node [RuboCop::AST::Node]
56
+ # @return [Boolean] true if matches
57
+ def_node_matcher :duration?, '(send { int float (send nil _) } DURATIONS)'
58
+
59
+ # @!method time_current?(node)
60
+ # Match Time.current
61
+ #
62
+ # @example source that matches
63
+ # Time.current
64
+ #
65
+ # @example source that matches
66
+ # ::Time.zone.now
67
+ #
68
+ # @param node [RuboCop::AST::Node]
69
+ # @return [Boolean] true if matches
70
+ def_node_matcher :time_current?, <<~PATTERN
71
+ {
72
+ (send (const _ :Time) :current)
73
+ (send (send (const _ :Time) :zone) :now)
74
+ }
75
+ PATTERN
76
+
77
+ def on_send(node)
78
+ duration_arithmetic_argument?(node) do |*operation|
79
+ add_offense(node) do |corrector|
80
+ corrector.replace(node.source_range, corrected_source(*operation))
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def corrected_source(operator, duration)
88
+ if operator == :-
89
+ "#{duration.source}.ago"
90
+ else
91
+ "#{duration.source}.from_now"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -7,6 +7,10 @@ module RuboCop
7
7
  # Use `find_by` instead of dynamic method.
8
8
  # See. https://rails.rubystyle.guide#find_by
9
9
  #
10
+ # @safety
11
+ # It is certainly unsafe when not configured properly, i.e. user-defined `find_by_xxx`
12
+ # method is not added to cop's `AllowedMethods`.
13
+ #
10
14
  # @example
11
15
  # # bad
12
16
  # User.find_by_name(name)
@@ -43,9 +43,22 @@ module RuboCop
43
43
  private
44
44
 
45
45
  def ignored?(node)
46
+ return true if active_model_error_where?(node.receiver)
47
+
46
48
  method_chain = node.each_node(:send).map(&:method_name)
49
+
47
50
  (cop_config['IgnoredMethods'].map(&:to_sym) & method_chain).any?
48
51
  end
52
+
53
+ def active_model_error_where?(node)
54
+ node.method?(:where) && active_model_error?(node.receiver)
55
+ end
56
+
57
+ def active_model_error?(node)
58
+ return false if node.nil?
59
+
60
+ node.send_type? && node.method?(:errors)
61
+ end
49
62
  end
50
63
  end
51
64
  end
@@ -67,7 +67,7 @@ module RuboCop
67
67
  private
68
68
 
69
69
  def in_routing_block?(node)
70
- !!node.each_ancestor(:block).detect { |block| ROUTING_METHODS.include?(block.send_node.method_name) }
70
+ !!node.each_ancestor(:block).detect { |block| ROUTING_METHODS.include?(block.method_name) }
71
71
  end
72
72
 
73
73
  def needs_conversion?(data)
@@ -23,22 +23,22 @@ module RuboCop
23
23
 
24
24
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
25
25
  (block
26
- ({send csend} _ :each_with_object (hash))
26
+ (call _ :each_with_object (hash))
27
27
  (args (arg $_el) (arg _memo))
28
- ({send csend} (lvar _memo) :[]= $!`_memo (lvar _el)))
28
+ (call (lvar _memo) :[]= $!`_memo (lvar _el)))
29
29
  PATTERN
30
30
 
31
31
  def_node_matcher :on_bad_to_h, <<~PATTERN
32
32
  (block
33
- ({send csend} _ :to_h)
33
+ (call _ :to_h)
34
34
  (args (arg $_el))
35
35
  (array $_ (lvar _el)))
36
36
  PATTERN
37
37
 
38
38
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
39
- ({send csend}
39
+ (call
40
40
  (block
41
- ({send csend} _ {:map :collect})
41
+ (call _ {:map :collect})
42
42
  (args (arg $_el))
43
43
  (array $_ (lvar _el)))
44
44
  :to_h)
@@ -49,7 +49,7 @@ module RuboCop
49
49
  (const _ :Hash)
50
50
  :[]
51
51
  (block
52
- ({send csend} _ {:map :collect})
52
+ (call _ {:map :collect})
53
53
  (args (arg $_el))
54
54
  (array $_ (lvar _el))))
55
55
  PATTERN