rubocop-rails 2.19.1 → 2.20.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b020fcc0db6203c33dfe23032cc2c6ec0df139e6abb048409bdf7439ea2d4183
4
- data.tar.gz: fc6e892161124d21b9067e01e6b86178857df27d14ae687f3f0fdcceb2b3d078
3
+ metadata.gz: c2b42b7139019411be72a5ddef55c1037712c10d65de30c86c70873ffafd845f
4
+ data.tar.gz: 10f1d00e705302bbee5f327dfb340c8618a81602e2c681cff80da1db78c450b0
5
5
  SHA512:
6
- metadata.gz: f09636066a282111462ab5cc3b93463ec0cd8a111e2882b6e7c21889da3b36958766f722926418439e48933d8791ce41c0edadc3e4a590c5f8caeb82f35ad5b1
7
- data.tar.gz: 40586400ad94701ee39ee71ab7280f7328889245f4786503cf5a851d158a05e4f736eea42c53f61164a420af8fab7e19854c6302237115a29efd3bdd1f19cc68
6
+ metadata.gz: 290449be7da9d2c0d1f1666e1d05a888ec52bda109609a9edaaa15c60a392015197066c69486404f8556a2bc316936a21cbcfc8080ca9b9a020d3fe321e66339
7
+ data.tar.gz: '0029ee6a3792e4c4e127a50ba8cbc59b0df5994820a04729968016b5eee2aa977f7550e5b3c5083aa64eee70b10807cbba727c372b799479ac7315b01ec083a9'
data/config/default.yml CHANGED
@@ -6,6 +6,7 @@ inherit_mode:
6
6
 
7
7
  AllCops:
8
8
  Exclude:
9
+ - app/assets/**/*
9
10
  - bin/*
10
11
  # Exclude db/schema.rb and db/[CONFIGURATION_NAMESPACE]_schema.rb by default.
11
12
  # See: https://guides.rubyonrails.org/active_record_multiple_databases.html#setting-up-your-application
@@ -47,6 +48,19 @@ Lint/NumberConversion:
47
48
  - in_milliseconds
48
49
  AllowedPatterns: []
49
50
 
51
+ Lint/RedundantSafeNavigation:
52
+ # Add `presence` and `present?` methods to the default of the RuboCop core.
53
+ # https://github.com/rubocop/rubocop/blob/v1.51.0/config/default.yml#L2148-L2159
54
+ AllowedMethods:
55
+ - instance_of?
56
+ - kind_of?
57
+ - is_a?
58
+ - eql?
59
+ - respond_to?
60
+ - equal?
61
+ - presence
62
+ - present?
63
+
50
64
  Rails:
51
65
  Enabled: true
52
66
  DocumentationBaseURL: https://docs.rubocop.org/rubocop-rails
@@ -146,8 +160,9 @@ Rails/AddColumnIndex:
146
160
  index might be used.
147
161
  Enabled: pending
148
162
  VersionAdded: '2.11'
163
+ VersionChanged: '2.20'
149
164
  Include:
150
- - db/migrate/*.rb
165
+ - db/**/*.rb
151
166
 
152
167
  Rails/AfterCommitOverride:
153
168
  Description: >-
@@ -235,12 +250,13 @@ Rails/BulkChangeTable:
235
250
  - https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html
236
251
  Enabled: true
237
252
  VersionAdded: '0.57'
253
+ VersionChanged: '2.20'
238
254
  Database: null
239
255
  SupportedDatabases:
240
256
  - mysql
241
257
  - postgresql
242
258
  Include:
243
- - db/migrate/*.rb
259
+ - db/**/*.rb
244
260
 
245
261
  Rails/CompactBlank:
246
262
  Description: 'Checks if collection can be blank-compacted with `compact_blank`.'
@@ -270,18 +286,22 @@ Rails/CreateTableWithTimestamps:
270
286
  when creating a new table.
271
287
  Enabled: true
272
288
  VersionAdded: '0.52'
289
+ VersionChanged: '2.20'
273
290
  Include:
274
- - db/migrate/*.rb
291
+ - db/**/*.rb
275
292
  Exclude:
276
293
  # Respect the `active_storage_variant_records` table of `*_create_active_storage_tables.active_storage.rb`
294
+ # and `*_create_active_storage_variant_records.active_storage.rb`
277
295
  # auto-generated by `bin/rails active_storage:install` even if `created_at` is not specified.
278
- - db/migrate/*_create_active_storage_tables.active_storage.rb
296
+ - db/**/*_create_active_storage_tables.active_storage.rb
297
+ - db/**/*_create_active_storage_variant_records.active_storage.rb
279
298
 
280
299
  Rails/Date:
281
300
  Description: >-
282
301
  Checks the correct usage of date aware methods,
283
302
  such as Date.today, Date.current etc.
284
303
  Enabled: true
304
+ SafeAutoCorrect: false
285
305
  VersionAdded: '0.30'
286
306
  VersionChanged: '2.11'
287
307
  # The value `strict` disallows usage of `Date.today`, `Date.current`,
@@ -632,8 +652,9 @@ Rails/MigrationClassName:
632
652
  Description: 'The class name of the migration should match its file name.'
633
653
  Enabled: pending
634
654
  VersionAdded: '2.14'
655
+ VersionChanged: '2.20'
635
656
  Include:
636
- - db/migrate/*.rb
657
+ - db/**/*.rb
637
658
 
638
659
  Rails/NegateInclude:
639
660
  Description: 'Prefer `collection.exclude?(obj)` over `!collection.include?(obj)`.'
@@ -647,8 +668,9 @@ Rails/NotNullColumn:
647
668
  Description: 'Do not add a NOT NULL column without a default value.'
648
669
  Enabled: true
649
670
  VersionAdded: '0.43'
671
+ VersionChanged: '2.20'
650
672
  Include:
651
- - db/migrate/*.rb
673
+ - db/**/*.rb
652
674
 
653
675
  Rails/OrderById:
654
676
  Description: >-
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- #
7
6
  # Use `assert_not` instead of `assert !`.
8
7
  #
9
8
  # @example
@@ -22,6 +22,9 @@ module RuboCop
22
22
  # And you can set a warning for `to_time` with `AllowToTime: false`.
23
23
  # `AllowToTime` is `true` by default to prevent false positive on `DateTime` object.
24
24
  #
25
+ # @safety
26
+ # This cop's autocorrection is unsafe because it may change handling time.
27
+ #
25
28
  # @example EnforcedStyle: flexible (default)
26
29
  # # bad
27
30
  # Date.today
@@ -51,6 +54,8 @@ module RuboCop
51
54
  # # bad
52
55
  # date.to_time
53
56
  class Date < Base
57
+ extend AutoCorrector
58
+
54
59
  include ConfigurableEnforcedStyle
55
60
 
56
61
  MSG = 'Do not use `Date.%<method_called>s` without zone. Use `Time.zone.%<day>s` instead.'
@@ -92,7 +97,9 @@ module RuboCop
92
97
 
93
98
  message = format(DEPRECATED_MSG, deprecated: method[:deprecated], relevant: method[:relevant])
94
99
 
95
- add_offense(node.loc.selector, message: message)
100
+ add_offense(node.loc.selector, message: message) do |corrector|
101
+ corrector.replace(node.loc.selector, method[:relevant].to_s)
102
+ end
96
103
  end
97
104
  end
98
105
 
@@ -108,7 +115,9 @@ module RuboCop
108
115
 
109
116
  message = format(MSG, method_called: method_name, day: day)
110
117
 
111
- add_offense(node.loc.selector, message: message)
118
+ add_offense(node.loc.selector, message: message) do |corrector|
119
+ corrector.replace(node.receiver.loc.name, 'Time.zone')
120
+ end
112
121
  end
113
122
 
114
123
  def extract_method_chain(node)
@@ -35,6 +35,8 @@ module RuboCop
35
35
  # Rails.root.join('app', 'models', 'goober').to_s
36
36
  #
37
37
  class FilePath < Base
38
+ extend AutoCorrector
39
+
38
40
  include ConfigurableEnforcedStyle
39
41
  include RangeHelp
40
42
 
@@ -56,13 +58,10 @@ module RuboCop
56
58
 
57
59
  def on_dstr(node)
58
60
  return unless rails_root_nodes?(node)
59
- return unless node.children.last.str_type?
60
-
61
- last_child_source = node.children.last.source
62
- return unless last_child_source.start_with?('.') || last_child_source.include?(File::SEPARATOR)
63
- return if last_child_source.start_with?(':')
61
+ return if dstr_separated_by_colon?(node)
64
62
 
65
- register_offense(node, require_to_s: true)
63
+ check_for_slash_after_rails_root_in_dstr(node)
64
+ check_for_extension_after_rails_root_join_in_dstr(node)
66
65
  end
67
66
 
68
67
  def on_send(node)
@@ -73,11 +72,33 @@ module RuboCop
73
72
 
74
73
  private
75
74
 
75
+ def check_for_slash_after_rails_root_in_dstr(node)
76
+ rails_root_index = find_rails_root_index(node)
77
+ slash_node = node.children[rails_root_index + 1]
78
+ return unless slash_node&.str_type? && slash_node.source.start_with?(File::SEPARATOR)
79
+
80
+ register_offense(node, require_to_s: false) do |corrector|
81
+ autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
82
+ end
83
+ end
84
+
85
+ def check_for_extension_after_rails_root_join_in_dstr(node)
86
+ rails_root_index = find_rails_root_index(node)
87
+ extension_node = node.children[rails_root_index + 1]
88
+ return unless extension_node?(extension_node)
89
+
90
+ register_offense(node, require_to_s: false) do |corrector|
91
+ autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
92
+ end
93
+ end
94
+
76
95
  def check_for_file_join_with_rails_root(node)
77
96
  return unless file_join_nodes?(node)
78
97
  return unless node.arguments.any? { |e| rails_root_nodes?(e) }
79
98
 
80
- register_offense(node, require_to_s: true)
99
+ register_offense(node, require_to_s: true) do |corrector|
100
+ autocorrect_file_join(corrector, node)
101
+ end
81
102
  end
82
103
 
83
104
  def check_for_rails_root_join_with_string_arguments(node)
@@ -87,7 +108,9 @@ module RuboCop
87
108
  return unless node.arguments.size > 1
88
109
  return unless node.arguments.all?(&:str_type?)
89
110
 
90
- register_offense(node, require_to_s: false)
111
+ register_offense(node, require_to_s: false) do |corrector|
112
+ autocorrect_rails_root_join_with_string_arguments(corrector, node)
113
+ end
91
114
  end
92
115
 
93
116
  def check_for_rails_root_join_with_slash_separated_path(node)
@@ -96,21 +119,22 @@ module RuboCop
96
119
  return unless rails_root_join_nodes?(node)
97
120
  return unless node.arguments.any? { |arg| string_with_slash?(arg) }
98
121
 
99
- register_offense(node, require_to_s: false)
122
+ register_offense(node, require_to_s: false) do |corrector|
123
+ autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
124
+ end
100
125
  end
101
126
 
102
127
  def string_with_slash?(node)
103
- node.str_type? && node.source.include?('/')
128
+ node.str_type? && node.source.include?(File::SEPARATOR)
104
129
  end
105
130
 
106
- def register_offense(node, require_to_s:)
131
+ def register_offense(node, require_to_s:, &block)
107
132
  line_range = node.loc.column...node.loc.last_column
108
133
  source_range = source_range(processed_source.buffer, node.first_line, line_range)
109
- require_to_s = false if node.dstr_type?
110
134
 
111
135
  message = build_message(require_to_s)
112
136
 
113
- add_offense(source_range, message: message)
137
+ add_offense(source_range, message: message, &block)
114
138
  end
115
139
 
116
140
  def build_message(require_to_s)
@@ -119,6 +143,95 @@ module RuboCop
119
143
 
120
144
  format(message_template, to_s: to_s)
121
145
  end
146
+
147
+ def dstr_separated_by_colon?(node)
148
+ node.children[1..].any? do |child|
149
+ child.str_type? && child.source.start_with?(':')
150
+ end
151
+ end
152
+
153
+ def autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
154
+ rails_root_node = node.children[rails_root_index].children.first
155
+ argument_source = extract_rails_root_join_argument_source(node, rails_root_index)
156
+ if rails_root_node.method?(:join)
157
+ append_argument(corrector, rails_root_node, argument_source)
158
+ else
159
+ replace_with_rails_root_join(corrector, rails_root_node, argument_source)
160
+ end
161
+ node.children[rails_root_index + 1..].each { |child| corrector.remove(child) }
162
+ end
163
+
164
+ def autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
165
+ rails_root_node = node.children[rails_root_index].children.first
166
+ return unless rails_root_node.arguments.last.str_type?
167
+
168
+ corrector.insert_before(rails_root_node.arguments.last.location.end, extension_node.source)
169
+ corrector.remove(extension_node)
170
+ end
171
+
172
+ def autocorrect_file_join(corrector, node)
173
+ corrector.replace(node.receiver, 'Rails.root')
174
+ corrector.remove(
175
+ range_with_surrounding_space(
176
+ range_with_surrounding_comma(
177
+ node.arguments.first.source_range,
178
+ :right
179
+ ),
180
+ side: :right
181
+ )
182
+ )
183
+ corrector.insert_after(node, '.to_s')
184
+ end
185
+
186
+ def autocorrect_rails_root_join_with_string_arguments(corrector, node)
187
+ corrector.replace(node.arguments.first, %("#{node.arguments.map(&:value).join('/')}"))
188
+ node.arguments[1..].each do |argument|
189
+ corrector.remove(
190
+ range_with_surrounding_comma(
191
+ range_with_surrounding_space(
192
+ argument.source_range,
193
+ side: :left
194
+ ),
195
+ :left
196
+ )
197
+ )
198
+ end
199
+ end
200
+
201
+ def autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
202
+ node.arguments.each do |argument|
203
+ next unless string_with_slash?(argument)
204
+
205
+ index = argument.source.index(File::SEPARATOR)
206
+ rest = inner_range_of(argument).adjust(begin_pos: index - 1)
207
+ corrector.remove(rest)
208
+ corrector.insert_after(argument, %(, "#{rest.source.delete_prefix(File::SEPARATOR)}"))
209
+ end
210
+ end
211
+
212
+ def inner_range_of(node)
213
+ node.location.end.with(begin_pos: node.location.begin.end_pos).adjust(end_pos: -1)
214
+ end
215
+
216
+ def find_rails_root_index(node)
217
+ node.children.index { |child| rails_root_nodes?(child) }
218
+ end
219
+
220
+ def append_argument(corrector, node, argument_source)
221
+ corrector.insert_after(node.arguments.last, %(, "#{argument_source}"))
222
+ end
223
+
224
+ def replace_with_rails_root_join(corrector, node, argument_source)
225
+ corrector.replace(node, %<Rails.root.join("#{argument_source}")>)
226
+ end
227
+
228
+ def extract_rails_root_join_argument_source(node, rails_root_index)
229
+ node.children[rails_root_index + 1..].map(&:source).join.delete_prefix(File::SEPARATOR)
230
+ end
231
+
232
+ def extension_node?(node)
233
+ node&.str_type? && node.source.start_with?('.')
234
+ end
122
235
  end
123
236
  end
124
237
  end
@@ -34,7 +34,7 @@ module RuboCop
34
34
  RESTRICT_ON_SEND = %i[each].freeze
35
35
 
36
36
  SCOPE_METHODS = %i[
37
- all eager_load includes joins left_joins left_outer_joins not preload
37
+ all eager_load includes joins left_joins left_outer_joins not or preload
38
38
  references unscoped where
39
39
  ].freeze
40
40
 
@@ -80,7 +80,11 @@ module RuboCop
80
80
  PATTERN
81
81
 
82
82
  def_node_matcher :flash_assignment?, <<~PATTERN
83
- (send (send nil? :flash) :[]= _ $str)
83
+ (send
84
+ {
85
+ (send nil? :flash)
86
+ (send (send nil? :flash) :now)
87
+ } :[]= _ $str)
84
88
  PATTERN
85
89
 
86
90
  def_node_search :mail_subject, <<~PATTERN
@@ -36,6 +36,10 @@ module RuboCop
36
36
  # if: -> { trusted_origin? && action_name != "admin" }
37
37
  # end
38
38
  class IgnoredSkipActionFilterOption < Base
39
+ extend AutoCorrector
40
+
41
+ include RangeHelp
42
+
39
43
  MSG = <<~MSG.chomp.freeze
40
44
  `%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
41
45
  MSG
@@ -60,9 +64,13 @@ module RuboCop
60
64
  options = options_hash(options)
61
65
 
62
66
  if if_and_only?(options)
63
- add_offense(options[:if], message: format(MSG, prefer: :only, ignore: :if))
67
+ add_offense(options[:if], message: format(MSG, prefer: :only, ignore: :if)) do |corrector|
68
+ remove_node_with_left_space_and_comma(corrector, options[:if])
69
+ end
64
70
  elsif if_and_except?(options)
65
- add_offense(options[:except], message: format(MSG, prefer: :if, ignore: :except))
71
+ add_offense(options[:except], message: format(MSG, prefer: :if, ignore: :except)) do |corrector|
72
+ remove_node_with_left_space_and_comma(corrector, options[:except])
73
+ end
66
74
  end
67
75
  end
68
76
 
@@ -81,6 +89,18 @@ module RuboCop
81
89
  def if_and_except?(options)
82
90
  options.key?(:if) && options.key?(:except)
83
91
  end
92
+
93
+ def remove_node_with_left_space_and_comma(corrector, node)
94
+ corrector.remove(
95
+ range_with_surrounding_comma(
96
+ range_with_surrounding_space(
97
+ node.source_range,
98
+ side: :left
99
+ ),
100
+ :left
101
+ )
102
+ )
103
+ end
84
104
  end
85
105
  end
86
106
  end
@@ -176,14 +176,14 @@ module RuboCop
176
176
  when :sym
177
177
  [node.value]
178
178
  when :array
179
- node.values.map do |v|
179
+ node.values.filter_map do |v|
180
180
  case v.type
181
181
  when :str
182
182
  v.str_content.to_sym
183
183
  when :sym
184
184
  v.value
185
185
  end
186
- end.compact
186
+ end
187
187
  else
188
188
  []
189
189
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
 
46
46
  def check_add_column(node)
47
47
  add_not_null_column?(node) do |type, pairs|
48
- return if type.value == :virtual || type.value == 'virtual'
48
+ return if type.respond_to?(:value) && (type.value == :virtual || type.value == 'virtual')
49
49
 
50
50
  check_pairs(pairs)
51
51
  end
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- #
7
6
  # Use `assert_not` methods instead of `refute` methods.
8
7
  #
9
8
  # @example EnforcedStyle: assert_not (default)
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- #
7
6
  # Checks SQL heredocs to use `.squish`.
8
7
  #
9
8
  # @safety
@@ -35,7 +35,7 @@ module RuboCop
35
35
  PATTERN
36
36
 
37
37
  def_node_search :change_column_null?, <<~PATTERN
38
- (send nil? :change_column_null {(sym %1) (str %1)} {(sym %2) (str %2)} false)
38
+ (send nil? :change_column_null %1 %2 false)
39
39
  PATTERN
40
40
 
41
41
  def on_send(node)
@@ -46,9 +46,7 @@ module RuboCop
46
46
 
47
47
  def_node = node.each_ancestor(:def, :defs).first
48
48
  table_node = table_node(node)
49
- if def_node && (table_node.nil? || change_column_null?(def_node, table_node.value, column_node.value))
50
- return
51
- end
49
+ return if def_node && (table_node.nil? || change_column_null?(def_node, table_node, column_node))
52
50
 
53
51
  add_offense(node)
54
52
  end
@@ -34,6 +34,11 @@ module RuboCop
34
34
  # throw if user.active?
35
35
  # end
36
36
  #
37
+ # # bad, as `with_lock` implicitly opens a transaction too
38
+ # ApplicationRecord.with_lock do
39
+ # break if user.active?
40
+ # end
41
+ #
37
42
  # # good
38
43
  # ApplicationRecord.transaction do
39
44
  # # Rollback
@@ -91,7 +96,9 @@ module RuboCop
91
96
  end
92
97
 
93
98
  def nested_block?(statement_node)
94
- !statement_node.ancestors.find(&:block_type?).method?(:transaction)
99
+ block_node = statement_node.ancestors.find(&:block_type?)
100
+
101
+ RESTRICT_ON_SEND.none? { |name| block_node.method?(name) }
95
102
  end
96
103
  end
97
104
  end
@@ -31,7 +31,7 @@ module RuboCop
31
31
  RESTRICT_ON_SEND = %i[validates].freeze
32
32
 
33
33
  def on_send(node)
34
- return unless uniqueness_part(node)
34
+ return if uniqueness_part(node)&.falsey_literal?
35
35
  return if condition_part?(node)
36
36
  return unless schema
37
37
 
@@ -83,21 +83,21 @@ module RuboCop
83
83
  private
84
84
 
85
85
  def build_columns(node)
86
- each_content(node).map do |child|
86
+ each_content(node).filter_map do |child|
87
87
  next unless child&.send_type?
88
88
  next if child.method?(:index)
89
89
 
90
90
  Column.new(child)
91
- end.compact
91
+ end
92
92
  end
93
93
 
94
94
  def build_indices(node)
95
- each_content(node).map do |child|
95
+ each_content(node).filter_map do |child|
96
96
  next unless child&.send_type?
97
97
  next unless child.method?(:index)
98
98
 
99
99
  Index.new(child)
100
- end.compact
100
+ end
101
101
  end
102
102
 
103
103
  def each_content(node, &block)
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Rails
5
5
  # This module holds the RuboCop Rails version information.
6
6
  module Version
7
- STRING = '2.19.1'
7
+ STRING = '2.20.1'
8
8
 
9
9
  def self.document_version
10
10
  STRING.match('\d+\.\d+').to_s
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.19.1
4
+ version: 2.20.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-04-16 00:00:00.000000000 Z
13
+ date: 2023-06-19 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -219,7 +219,7 @@ metadata:
219
219
  homepage_uri: https://docs.rubocop.org/rubocop-rails/
220
220
  changelog_uri: https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md
221
221
  source_code_uri: https://github.com/rubocop/rubocop-rails/
222
- documentation_uri: https://docs.rubocop.org/rubocop-rails/2.19/
222
+ documentation_uri: https://docs.rubocop.org/rubocop-rails/2.20/
223
223
  bug_tracker_uri: https://github.com/rubocop/rubocop-rails/issues
224
224
  rubygems_mfa_required: 'true'
225
225
  post_install_message:
@@ -230,7 +230,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
230
230
  requirements:
231
231
  - - ">="
232
232
  - !ruby/object:Gem::Version
233
- version: 2.6.0
233
+ version: 2.7.0
234
234
  required_rubygems_version: !ruby/object:Gem::Requirement
235
235
  requirements:
236
236
  - - ">="