rubocop-lts-ruby 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.
data/Rakefile ADDED
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ # kettle-jem:freeze
4
+ # To retain chunks of comments & code during kettle-jem templating:
5
+ # Wrap custom sections with freeze markers (e.g., as above and below this comment chunk).
6
+ # kettle-jem will then preserve content between those markers across template runs.
7
+ # kettle-jem:unfreeze
8
+
9
+ # rubocop-lts-ruby Rakefile v7.1.1 - 2026-08-09
10
+ # Ruby 2.3 (Safe Navigation) or higher required
11
+ #
12
+ # See LICENSE.md for license information.
13
+ #
14
+ # Copyright (c) 2026 Peter H. Boling (galtzo.com)
15
+ #
16
+ # Expected to work in any project that uses Bundler.
17
+ #
18
+ # Sets up tasks for appraisal2, floss_funding, kettle-jem, kettle-dev, rspec, minitest, rubocop_gradual, reek, yard, and stone_checksums.
19
+ #
20
+ # rake appraisal:install # Install Appraisal gemfiles (initial setup...
21
+ # rake appraisal:reset # Delete Appraisal lockfiles (gemfiles/*.gemfile.lock)
22
+ # rake appraisal:generate # Generate Appraisal gemfiles without resolving...
23
+ # rake appraisal:update # Generate and update Appraisal gemfiles
24
+ # rake bench # Run all benchmarks (alias for bench:run)
25
+ # rake bench:list # List available benchmark scripts
26
+ # rake bench:run # Run all benchmark scripts (skips on CI)
27
+ # rake build:generate_checksums # Generate both SHA256 & SHA512 checksums i...
28
+ # rake bundle:audit:check # Checks the Gemfile.lock for insecure depe...
29
+ # rake bundle:audit:update # Updates the bundler-audit vulnerability d...
30
+ # rake ci:act[opt] # Run 'act' with a selected workflow
31
+ # rake coverage # Run specs w/ coverage and open results in...
32
+ # rake default # Default tasks aggregator
33
+ # rake install # Build and install rubocop-lts-ruby-1.0.0.gem in...
34
+ # rake install:local # Build and install rubocop-lts-ruby-1.0.0.gem in...
35
+ # rake kettle:jem:install # Internal target used by `kettle-jem install`
36
+ # rake kettle:jem:selftest # Self-test: template rubocop-lts-ruby against itse...
37
+ # rake kettle:jem:template # Internal target used by scoped `kettle-jem template --only`
38
+ # rake reek # Check for code smells
39
+ # rake reek:update # Run reek and store the output into the RE...
40
+ # rake release[remote] # Create tag v1.0.0 and build and push kett...
41
+ # rake rubocop_gradual # Run RuboCop Gradual
42
+ # rake rubocop_gradual:autocorrect # Run RuboCop Gradual with autocorrect (onl...
43
+ # rake rubocop_gradual:autocorrect_all # Run RuboCop Gradual with autocorrect (saf...
44
+ # rake rubocop_gradual:check # Run RuboCop Gradual to check the lock file
45
+ # rake rubocop_gradual:force_update # Run RuboCop Gradual to force update the l...
46
+ # rake rubocop_gradual_debug # Run RuboCop Gradual
47
+ # rake rubocop_gradual_debug:autocorrect # Run RuboCop Gradual with autocorrect (onl...
48
+ # rake rubocop_gradual_debug:autocorrect_all # Run RuboCop Gradual with autocorrect (saf...
49
+ # rake rubocop_gradual_debug:check # Run RuboCop Gradual to check the lock file
50
+ # rake rubocop_gradual_debug:force_update # Run RuboCop Gradual to force update the l...
51
+ # rake spec # Run RSpec code examples
52
+ # rake test # Run tests
53
+ # rake yard # Generate YARD Documentation
54
+ # rake yard:lint # Lint YARD Documentation
55
+ #
56
+
57
+ # simplecov:disable
58
+ require "bundler/gem_tasks" if !Dir[File.join(__dir__, "*.gemspec")].empty?
59
+ # simplecov:enable
60
+
61
+ # Define a base default task early so other files can enhance it.
62
+ desc "Default tasks aggregator"
63
+ task :default do
64
+ puts "Default task complete."
65
+ end
66
+
67
+ # simplecov:disable
68
+ ### MONOREPO FAMILY TASKS
69
+ if Dir.exist?(File.join(__dir__, "gems"))
70
+ def family_gem_dirs
71
+ Dir.glob(File.join(__dir__, "gems", "*", "*.gemspec"))
72
+ .map { |path| File.dirname(path) }
73
+ .uniq
74
+ .sort_by { |path| File.basename(path) }
75
+ end
76
+
77
+ def run_kettle_family(*args)
78
+ sh("bundle", "exec", "kettle-family", *args)
79
+ end
80
+
81
+ namespace :family do
82
+ desc "List released Ruby subgems"
83
+ task :list do
84
+ family_gem_dirs.each { |path| puts File.basename(path) }
85
+ end
86
+
87
+ desc "Run release readiness checks for the Ruby gem family"
88
+ task :readiness do
89
+ run_kettle_family("check")
90
+ end
91
+
92
+ desc "Run tests for the Ruby gem family"
93
+ task :test do
94
+ run_kettle_family("test", "--execute")
95
+ end
96
+
97
+ desc "Run lint for the Ruby gem family"
98
+ task :lint do
99
+ run_kettle_family("lint", "--execute")
100
+ end
101
+
102
+ desc "Generate YARD docs for the Ruby gem family"
103
+ task :docs do
104
+ run_kettle_family("docs", "--execute")
105
+ end
106
+
107
+ desc "Report release state for the Ruby gem family"
108
+ task :release_state do
109
+ run_kettle_family("release-state")
110
+ end
111
+
112
+ desc "Run the Ruby gem family release planner"
113
+ task :release do
114
+ run_kettle_family("release")
115
+ end
116
+
117
+ desc "Execute the Ruby gem family release"
118
+ task :release_execute do
119
+ run_kettle_family("release", "--execute")
120
+ end
121
+ end
122
+ end
123
+ # simplecov:enable
124
+
125
+ # External gems that define tasks - add here!
126
+ begin
127
+ require "kettle/dev"
128
+ Kettle::Dev.install_tasks unless Kettle::Dev::RUNNING_AS == "rake"
129
+ rescue LoadError
130
+ warn("NOTE: kettle-dev isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
131
+ end
132
+
133
+ ### DUPLICATE DRIFT TASKS
134
+ begin
135
+ require "kettle/drift"
136
+ Kettle::Drift.install_tasks
137
+ rescue LoadError
138
+ desc("(stub) kettle:drift:check is unavailable")
139
+ task("kettle:drift:check") do
140
+ warn("NOTE: kettle-drift isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
141
+ end
142
+ desc("(stub) kettle:drift:update is unavailable")
143
+ task("kettle:drift:update") do
144
+ warn("NOTE: kettle-drift isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
145
+ end
146
+ desc("(stub) kettle:drift:force_update is unavailable")
147
+ task("kettle:drift:force_update") do
148
+ warn("NOTE: kettle-drift isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
149
+ end
150
+ desc("(stub) kettle:drift is unavailable")
151
+ task("kettle:drift" => "kettle:drift:update")
152
+ end
153
+
154
+ ### TEMPLATING TASKS
155
+ # These tasks are installed for the `kettle-jem` executable. Run full templating
156
+ # through `kettle-jem install`; use `kettle-jem template --only PATH` only for
157
+ # scoped file updates. The executable prepares the environment and then
158
+ # delegates here when rake orchestration is needed.
159
+ kettle_jem_selftest_unavailable_note = nil
160
+ begin
161
+ require "kettle/jem"
162
+ if Kettle::Jem.respond_to?(:install_tasks)
163
+ Kettle::Jem.install_tasks
164
+ else
165
+ kettle_jem_selftest_unavailable_note = "NOTE: kettle-jem #{Kettle::Jem::Version::VERSION} does not provide rake tasks in this environment"
166
+ end
167
+ rescue LoadError
168
+ kettle_jem_selftest_unavailable_note = "NOTE: kettle-jem isn't installed, or is disabled for #{RUBY_VERSION} in the current environment"
169
+ end
170
+
171
+ if kettle_jem_selftest_unavailable_note
172
+ desc("(stub) kettle:jem:selftest is unavailable")
173
+ task("kettle:jem:selftest") do
174
+ warn(kettle_jem_selftest_unavailable_note)
175
+ end
176
+ end
177
+
178
+ ### RELEASE TASKS
179
+ # Setup stone_checksums
180
+ begin
181
+ require "stone_checksums"
182
+ rescue LoadError
183
+ desc("(stub) build:generate_checksums is unavailable")
184
+ task("build:generate_checksums") do
185
+ warn("NOTE: stone_checksums isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
186
+ end
187
+ end
188
+ # frozen_string_literal: true
data/config/base.yml ADDED
@@ -0,0 +1,12 @@
1
+ # This plugin owns its API-gating rules only. The consuming RuboCop
2
+ # configuration remains the sole owner of AllCops: TargetRubyVersion.
3
+ require:
4
+ - rubocop/lts/ruby/cop/lint_lts_ruby/unavailable_method
5
+
6
+ Lint/LtsRuby/UnavailableMethod:
7
+ Description: >-
8
+ Detect explicit calls to Ruby APIs that were introduced after the configured
9
+ TargetRubyVersion.
10
+ Enabled: true
11
+ VersionAdded: '0.1'
12
+ AllowedMethods: []
@@ -0,0 +1,383 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems/version"
4
+
5
+ module RuboCop
6
+ module Lts
7
+ module Ruby
8
+ # Runtime APIs whose availability is tied to the Ruby release that added them.
9
+ # Keep entries explicit until a versioned API source can be made authoritative.
10
+ module Catalog
11
+ Entry = Data.define(:owner, :method_name, :introduced_in, :receiver_type)
12
+
13
+ def self.entry(owner, method_name, introduced_in, receiver_type: :instance)
14
+ Entry.new(owner, method_name, Gem::Version.new(introduced_in), receiver_type)
15
+ end
16
+
17
+ ENTRIES = [
18
+ entry("ARGF", :codepoints, "2.0"),
19
+ entry("ARGF", :each_codepoint, "2.0"),
20
+ entry("Array", :append, "2.5"),
21
+ entry("Array", :bsearch, "2.0"),
22
+ entry("Array", :bsearch_index, "2.3"),
23
+ entry("Array", :fetch_values, "3.4"),
24
+ entry("Array", :prepend, "2.5"),
25
+ entry("Array", :dig, "2.3"),
26
+ entry("Array", :difference, "2.6"),
27
+ entry("Array", :filter, "2.6"),
28
+ entry("Array", :filter!, "2.6"),
29
+ entry("Array", :intersection, "2.7"),
30
+ entry("Array", :intersect?, "3.1"),
31
+ entry("Array", :minmax, "2.7"),
32
+ entry("Array", :sum, "2.4"),
33
+ entry("Array", :to_h, "2.1"),
34
+ entry("Array", :union, "2.6"),
35
+ entry("Binding", :local_variable_defined?, "2.1"),
36
+ entry("Binding", :local_variable_get, "2.1"),
37
+ entry("Binding", :local_variable_set, "2.1"),
38
+ entry("Binding", :local_variables, "2.2"),
39
+ entry("Binding", :receiver, "2.2"),
40
+ entry("Binding", :source_location, "2.6"),
41
+ entry("Class", :attached_object, "3.2"),
42
+ entry("Class", :subclasses, "3.1"),
43
+ entry("Comparable", :clamp, "2.4"),
44
+ entry("Complex", :<=>, "2.7"),
45
+ entry("Coverage", :peek_result, "2.3", receiver_type: :constant),
46
+ entry("Coverage", :line_stub, "2.6", receiver_type: :constant),
47
+ entry("Coverage", :supported?, "3.2", receiver_type: :constant),
48
+ entry("Date", :jisx0301, "2.7"),
49
+ entry("Date", :jisx0301, "2.7", receiver_type: :constant),
50
+ entry("Dir", :children, "2.5", receiver_type: :constant),
51
+ entry("Dir", :chdir, "3.3"),
52
+ entry("Dir", :each_child, "2.5", receiver_type: :constant),
53
+ entry("Dir", :empty?, "2.4", receiver_type: :constant),
54
+ entry("Dir", :fchdir, "3.3", receiver_type: :constant),
55
+ entry("Dir", :fileno, "2.2"),
56
+ entry("Dir", :for_fd, "3.3", receiver_type: :constant),
57
+ entry("Enumerable", :chain, "2.6"),
58
+ entry("Enumerable", :chunk_while, "2.3"),
59
+ entry("Enumerable", :compact, "3.1"),
60
+ entry("Enumerable", :filter, "2.6"),
61
+ entry("Enumerable", :filter_map, "2.7"),
62
+ entry("Enumerable", :grep_v, "2.3"),
63
+ entry("Enumerable", :lazy, "2.0"),
64
+ entry("Enumerable", :slice_after, "2.2"),
65
+ entry("Enumerable", :slice_when, "2.2"),
66
+ entry("Enumerable", :sum, "2.4"),
67
+ entry("Enumerable", :tally, "2.7"),
68
+ entry("Enumerable", :to_h, "2.1"),
69
+ entry("Enumerable", :uniq, "2.4"),
70
+ entry("Enumerator", :size, "2.0"),
71
+ entry("Enumerator", :+, "2.6"),
72
+ entry("Enumerator", :produce, "2.7", receiver_type: :constant),
73
+ entry("Enumerator", :product, "3.2", receiver_type: :constant),
74
+ entry("Enumerator::Lazy", :chunk_while, "2.4"),
75
+ entry("Enumerator::Lazy", :compact, "3.1"),
76
+ entry("Enumerator::Lazy", :eager, "2.7"),
77
+ entry("Enumerator::Lazy", :filter, "2.6"),
78
+ entry("Enumerator::Lazy", :grep_v, "2.3"),
79
+ entry("Enumerator::Lazy", :uniq, "2.4"),
80
+ entry("Enumerator::Yielder", :to_proc, "2.7"),
81
+ entry("Exception", :cause, "2.1"),
82
+ entry("Exception", :detailed_message, "3.2"),
83
+ entry("Exception", :full_message, "2.5"),
84
+ entry("ENV", :except, "3.0", receiver_type: :constant),
85
+ entry("ERB::Escape", :html_escape, "3.2", receiver_type: :constant),
86
+ entry("Etc", :confstr, "2.2", receiver_type: :constant),
87
+ entry("Etc", :nprocessors, "2.2", receiver_type: :constant),
88
+ entry("Etc", :sysconf, "2.2", receiver_type: :constant),
89
+ entry("Etc", :uname, "2.2", receiver_type: :constant),
90
+ entry("Date", :deconstruct_keys, "3.2"),
91
+ entry("DateTime", :deconstruct_keys, "3.2"),
92
+ entry("Fiber", :[], "3.2", receiver_type: :constant),
93
+ entry("Fiber", :[]=, "3.2", receiver_type: :constant),
94
+ entry("Fiber", :backtrace, "3.0"),
95
+ entry("Fiber", :backtrace_locations, "3.0"),
96
+ entry("Fiber", :blocking?, "3.0"),
97
+ entry("Fiber", :blocking?, "3.0", receiver_type: :constant),
98
+ entry("Fiber", :kill, "3.3"),
99
+ entry("Fiber", :raise, "2.7"),
100
+ entry("Fiber", :scheduler, "3.0", receiver_type: :constant),
101
+ entry("Fiber", :set_scheduler, "3.0", receiver_type: :constant),
102
+ entry("Fiber", :storage, "3.2"),
103
+ entry("Fiber", :"storage=", "3.2"),
104
+ entry("Fiber::Scheduler", :blocking_operation_wait, "3.4"),
105
+ entry("Fiber::Scheduler", :io_select, "3.2"),
106
+ entry("File", :absolute_path?, "2.7", receiver_type: :constant),
107
+ entry("File", :birthtime, "2.2"),
108
+ entry("File", :birthtime, "2.2", receiver_type: :constant),
109
+ entry("File", :empty?, "2.4", receiver_type: :constant),
110
+ entry("File", :lutime, "2.5", receiver_type: :constant),
111
+ entry("File", :mkfifo, "2.3", receiver_type: :constant),
112
+ entry("File::Stat", :birthtime, "2.2"),
113
+ entry("FileUtils", :cp_lr, "2.6"),
114
+ entry("FileUtils", :cp_lr, "2.6", receiver_type: :constant),
115
+ entry("FileUtils", :ln_sr, "3.2"),
116
+ entry("FileUtils", :ln_sr, "3.2", receiver_type: :constant),
117
+ entry("Float", :next_float, "2.2"),
118
+ entry("Float", :prev_float, "2.2"),
119
+ entry("FrozenError", :receiver, "2.7"),
120
+ entry("GC", :auto_compact, "3.0", receiver_type: :constant),
121
+ entry("GC", :"auto_compact=", "3.0", receiver_type: :constant),
122
+ entry("GC", :compact, "2.7", receiver_type: :constant),
123
+ entry("GC", :latest_gc_info, "2.2", receiver_type: :constant),
124
+ entry("GC", :config, "3.4", receiver_type: :constant),
125
+ entry("GC", :measure_total_time, "3.1", receiver_type: :constant),
126
+ entry("GC", :"measure_total_time=", "3.1", receiver_type: :constant),
127
+ entry("GC", :total_time, "3.1", receiver_type: :constant),
128
+ entry("GC::Profiler", :raw_data, "2.0", receiver_type: :constant),
129
+ entry("Hash", :compact, "2.4"),
130
+ entry("Hash", :compact!, "2.4"),
131
+ entry("Hash", :dig, "2.3"),
132
+ entry("Hash", :except, "3.0"),
133
+ entry("Hash", :fetch_values, "2.3"),
134
+ entry("Hash", :filter, "2.6"),
135
+ entry("Hash", :filter!, "2.6"),
136
+ entry("Hash", :slice, "2.5"),
137
+ entry("Hash", :to_h, "2.0"),
138
+ entry("Hash", :to_proc, "2.3"),
139
+ entry("Hash", :transform_keys, "2.5"),
140
+ entry("Hash", :transform_keys!, "2.5"),
141
+ entry("Hash", :transform_values, "2.4"),
142
+ entry("Hash", :transform_values!, "2.4"),
143
+ entry("IO", :path, "3.2"),
144
+ entry("IO", :cooked, "2.0"),
145
+ entry("IO", :cooked!, "2.0"),
146
+ entry("IO", :pathconf, "2.2"),
147
+ entry("IO", :pread, "2.5"),
148
+ entry("IO", :pwrite, "2.5"),
149
+ entry("IO", :set_encoding_by_bom, "2.7"),
150
+ entry("IO", :timeout, "3.2"),
151
+ entry("IO", :"timeout=", "3.2"),
152
+ entry("IO", :wait_readable, "2.0"),
153
+ entry("IO", :wait_writable, "2.0"),
154
+ entry("Integer", :allbits?, "2.5"),
155
+ entry("Integer", :anybits?, "2.5"),
156
+ entry("Integer", :bit_length, "2.1"),
157
+ entry("Integer", :ceildiv, "3.2"),
158
+ entry("Integer", :digits, "2.4"),
159
+ entry("Integer", :nobits?, "2.5"),
160
+ entry("Integer", :sqrt, "2.5", receiver_type: :constant),
161
+ entry("Integer", :try_convert, "3.1", receiver_type: :constant),
162
+ entry("KeyError", :key, "2.5"),
163
+ entry("KeyError", :receiver, "2.5"),
164
+ entry("Logger", :reopen, "2.3"),
165
+ entry("LoadError", :path, "2.0"),
166
+ entry("Kernel", :__dir__, "2.0"),
167
+ entry("Kernel", :Hash, "2.0"),
168
+ entry("Kernel", :caller_locations, "2.0"),
169
+ entry("Kernel", :itself, "2.2"),
170
+ entry("Kernel", :singleton_method, "2.1"),
171
+ entry("MatchData", :byteoffset, "3.2"),
172
+ entry("MatchData", :deconstruct, "3.2"),
173
+ entry("MatchData", :deconstruct_keys, "3.2"),
174
+ entry("MatchData", :match, "3.1"),
175
+ entry("MatchData", :match_length, "3.1"),
176
+ entry("MatchData", :named_captures, "2.4"),
177
+ entry("Matrix", :[]=, "2.6"),
178
+ entry("Matrix", :adjugate, "2.2"),
179
+ entry("Matrix", :antisymmetric?, "2.6"),
180
+ entry("Matrix", :cofactor, "2.2"),
181
+ entry("Matrix", :first_minor, "2.2"),
182
+ entry("Matrix", :hstack, "2.2"),
183
+ entry("Matrix", :independent?, "2.2"),
184
+ entry("Matrix", :laplace_expansion, "2.2"),
185
+ entry("Matrix", :map!, "2.6"),
186
+ entry("Matrix", :collect!, "2.6"),
187
+ entry("Matrix", :skew_symmetric?, "2.6"),
188
+ entry("Matrix", :vstack, "2.2"),
189
+ entry("Matrix", :hstack, "2.2", receiver_type: :constant),
190
+ entry("Matrix", :independent?, "2.2", receiver_type: :constant),
191
+ entry("Matrix", :vstack, "2.2", receiver_type: :constant),
192
+ entry("Method", :===, "2.5"),
193
+ entry("Method", :<<, "2.6"),
194
+ entry("Method", :curry, "2.2"),
195
+ entry("Method", :private?, "3.1"),
196
+ entry("Method", :protected?, "3.1"),
197
+ entry("Method", :public?, "3.1"),
198
+ entry("Method", :super_method, "2.2"),
199
+ entry("Method", :>>, "2.6"),
200
+ entry("Module", :const_added, "3.2"),
201
+ entry("Module", :const_source_location, "2.7"),
202
+ entry("Module", :deprecate_constant, "2.3"),
203
+ entry("Module", :prepend, "2.0"),
204
+ entry("Module", :prepend_features, "2.0"),
205
+ entry("Module", :prepended, "2.0"),
206
+ entry("Module", :refine, "2.0"),
207
+ entry("Module", :refinements, "3.2"),
208
+ entry("Module", :ruby2_keywords, "2.7"),
209
+ entry("Module", :set_temporary_name, "3.3"),
210
+ entry("Module", :singleton_class?, "2.1"),
211
+ entry("Module", :undefined_instance_methods, "3.2"),
212
+ entry("Module", :using, "2.1"),
213
+ entry("Module", :used_modules, "2.4"),
214
+ entry("Module", :used_refinements, "3.2", receiver_type: :constant),
215
+ entry("Mutex", :owned?, "2.0"),
216
+ entry("NameError", :receiver, "2.3"),
217
+ entry("Net::FTP", :features, "2.7"),
218
+ entry("Net::FTP", :mlsd, "2.3"),
219
+ entry("Net::FTP", :mlst, "2.3"),
220
+ entry("Net::FTP", :option, "2.7"),
221
+ entry("Net::HTTP", :local_host, "2.0"),
222
+ entry("Net::HTTP", :local_port, "2.0"),
223
+ entry("Net::HTTP", :post, "2.4", receiver_type: :constant),
224
+ entry("Net::HTTP", :verify_hostname, "3.0"),
225
+ entry("Net::HTTP", :"verify_hostname=", "3.0"),
226
+ entry("Net::IMAP", :default_port, "2.0", receiver_type: :constant),
227
+ entry("Net::IMAP", :default_imap_port, "2.0", receiver_type: :constant),
228
+ entry("Net::IMAP", :default_tls_port, "2.0", receiver_type: :constant),
229
+ entry("Net::IMAP", :default_ssl_port, "2.0", receiver_type: :constant),
230
+ entry("Net::IMAP", :default_imaps_port, "2.0", receiver_type: :constant),
231
+ entry("Net::SMTP", :rset, "2.1"),
232
+ entry("NilClass", :to_h, "2.0"),
233
+ entry("Numeric", :finite?, "2.4"),
234
+ entry("Numeric", :infinite?, "2.4"),
235
+ entry("Numeric", :negative?, "2.3"),
236
+ entry("Numeric", :positive?, "2.3"),
237
+ entry("Object", :then, "2.6"),
238
+ entry("Object", :yield_self, "2.5"),
239
+ entry("ObjectSpace", :reachable_objects_from, "2.0", receiver_type: :constant),
240
+ entry("ObjectSpace::WeakMap", :delete, "3.3"),
241
+ entry("OpenStruct", :[], "2.0"),
242
+ entry("OpenStruct", :[]=, "2.0"),
243
+ entry("OpenStruct", :each_pair, "2.0"),
244
+ entry("OpenStruct", :eql?, "2.0"),
245
+ entry("OpenStruct", :hash, "2.0"),
246
+ entry("OpenStruct", :to_h, "2.0"),
247
+ entry("Pathname", :empty?, "2.4"),
248
+ entry("Pathname", :birthtime, "2.2"),
249
+ entry("Pathname", :binwrite, "2.1"),
250
+ entry("Pathname", :lutime, "3.2"),
251
+ entry("Pathname", :write, "2.1"),
252
+ entry("Process", :argv0, "2.1", receiver_type: :constant),
253
+ entry("Process", :clock_getres, "2.1", receiver_type: :constant),
254
+ entry("Process", :clock_gettime, "2.1", receiver_type: :constant),
255
+ entry("Process", :getsid, "2.0", receiver_type: :constant),
256
+ entry("Process", :last_status, "2.5", receiver_type: :constant),
257
+ entry("Process", :setproctitle, "2.1", receiver_type: :constant),
258
+ entry("Process", :warmup, "3.3", receiver_type: :constant),
259
+ entry("Random", :bytes, "2.6", receiver_type: :constant),
260
+ entry("Readline", :quoting_detection_proc, "2.4", receiver_type: :constant),
261
+ entry("Readline", :quoting_detection_proc=, "2.4", receiver_type: :constant),
262
+ entry("Resolv::DNS", :timeouts=, "2.0"),
263
+ entry("Ripper", :state, "2.5"),
264
+ entry("Proc", :<<, "2.6"),
265
+ entry("Proc", :>>, "2.6"),
266
+ entry("Proc", :ruby2_keywords, "2.7"),
267
+ entry("Range", :bsearch, "2.0"),
268
+ entry("Range", :%, "2.6"),
269
+ entry("Range", :minmax, "2.7"),
270
+ entry("Range", :overlap?, "3.3"),
271
+ entry("Range", :size, "2.0"),
272
+ entry("Regexp", :linear_time?, "3.2", receiver_type: :constant),
273
+ entry("Regexp", :match?, "2.4"),
274
+ entry("Regexp", :timeout, "3.2", receiver_type: :constant),
275
+ entry("Regexp", :"timeout=", "3.2", receiver_type: :constant),
276
+ entry("Refinement", :refined_class, "3.2"),
277
+ entry("Refinement", :target, "3.3"),
278
+ entry("Signal", :signame, "2.0", receiver_type: :constant),
279
+ entry("Set", :<=>, "3.0"),
280
+ entry("Set", :compare_by_identity, "2.4"),
281
+ entry("Set", :compare_by_identity?, "2.4"),
282
+ entry("Set", :join, "3.0"),
283
+ entry("Set", :===, "2.5"),
284
+ entry("Set", :reset, "2.5"),
285
+ entry("Set", :to_s, "2.5"),
286
+ entry("SecureRandom", :alphanumeric, "2.5", receiver_type: :constant),
287
+ entry("String", :+@, "2.3"),
288
+ entry("String", :-@, "2.3"),
289
+ entry("String", :b, "2.0"),
290
+ entry("String", :byteindex, "3.2"),
291
+ entry("String", :byterindex, "3.2"),
292
+ entry("String", :bytesplice, "3.2"),
293
+ entry("String", :append_as_bytes, "3.4"),
294
+ entry("String", :casecmp?, "2.4"),
295
+ entry("String", :concat, "2.4"),
296
+ entry("String", :delete_prefix, "2.5"),
297
+ entry("String", :delete_prefix!, "2.5"),
298
+ entry("String", :delete_suffix, "2.5"),
299
+ entry("String", :delete_suffix!, "2.5"),
300
+ entry("String", :dedup, "3.2"),
301
+ entry("String", :each_grapheme_cluster, "2.5"),
302
+ entry("String", :grapheme_clusters, "2.5"),
303
+ entry("String", :match?, "2.4"),
304
+ entry("String", :prepend, "2.4"),
305
+ entry("String", :scrub, "2.1"),
306
+ entry("String", :scrub!, "2.1"),
307
+ entry("String", :unicode_normalize, "2.2"),
308
+ entry("String", :unicode_normalize!, "2.2"),
309
+ entry("String", :unicode_normalized?, "2.2"),
310
+ entry("String", :undump, "2.5"),
311
+ entry("String", :unpack1, "2.4"),
312
+ entry("Struct", :dig, "2.3"),
313
+ entry("Struct", :to_h, "2.0"),
314
+ entry("StringScanner", :captures, "2.5"),
315
+ entry("StringScanner", :size, "2.5"),
316
+ entry("StringScanner", :values_at, "2.5"),
317
+ entry("Symbol", :casecmp?, "2.4"),
318
+ entry("Symbol", :match, "2.4"),
319
+ entry("Symbol", :match?, "2.4"),
320
+ entry("Symbol", :name, "3.0"),
321
+ entry("Symbol", :start_with?, "2.7"),
322
+ entry("Symbol", :end_with?, "2.7"),
323
+ entry("Thread", :backtrace_locations, "2.0"),
324
+ entry("Thread", :each_caller_location, "3.2", receiver_type: :constant),
325
+ entry("Thread", :fetch, "2.5"),
326
+ entry("Thread", :ignore_deadlock, "3.0", receiver_type: :constant),
327
+ entry("Thread", :name, "2.3"),
328
+ entry("Thread", :"name=", "2.3"),
329
+ entry("Thread", :native_thread_id, "3.1"),
330
+ entry("Thread", :pending_interrupt?, "2.0"),
331
+ entry("Thread", :pending_interrupt?, "2.0", receiver_type: :constant),
332
+ entry("Thread", :report_on_exception, "2.4"),
333
+ entry("Thread", :"report_on_exception=", "2.4"),
334
+ entry("Thread", :report_on_exception, "2.4", receiver_type: :constant),
335
+ entry("Thread", :"report_on_exception=", "2.4", receiver_type: :constant),
336
+ entry("Thread", :thread_variable?, "2.0"),
337
+ entry("Thread", :thread_variable_get, "2.0"),
338
+ entry("Thread", :thread_variable_set, "2.0"),
339
+ entry("Thread", :thread_variables, "2.0"),
340
+ entry("Thread", :handle_interrupt, "2.0", receiver_type: :constant),
341
+ entry("Thread::Backtrace", :limit, "3.1", receiver_type: :constant),
342
+ entry("Socket::ResolutionError", :error_code, "3.4"),
343
+ entry("Time", :deconstruct_keys, "3.2"),
344
+ entry("Time", :ceil, "2.7"),
345
+ entry("Time", :floor, "2.7"),
346
+ entry("UnboundMethod", :private?, "3.1"),
347
+ entry("UnboundMethod", :protected?, "3.1"),
348
+ entry("UnboundMethod", :public?, "3.1"),
349
+ entry("UnboundMethod", :bind_call, "2.7"),
350
+ entry("Vector", :[]=, "2.6"),
351
+ entry("Vector", :angle_with, "2.2"),
352
+ entry("Vector", :collect!, "2.6"),
353
+ entry("Vector", :cross, "2.2"),
354
+ entry("Vector", :cross_product, "2.1"),
355
+ entry("Vector", :dot, "2.2"),
356
+ entry("Vector", :map!, "2.6"),
357
+ entry("Vector", :round, "2.3"),
358
+ entry("Vector", :basis, "2.2", receiver_type: :constant),
359
+ entry("Warning", :[], "2.7", receiver_type: :constant),
360
+ entry("Warning", :[]=, "2.7", receiver_type: :constant),
361
+ entry("Warning", :categories, "3.4", receiver_type: :constant),
362
+ entry("SyntaxError", :path, "3.2"),
363
+ entry("TracePoint", :allow_reentry, "3.1", receiver_type: :constant),
364
+ entry("TracePoint", :eval_script, "2.6"),
365
+ entry("TracePoint", :instruction_sequence, "2.6"),
366
+ entry("TracePoint", :parameters, "2.6"),
367
+ entry("Thread::Queue", :close, "2.3"),
368
+ entry("Data", :define, "3.2", receiver_type: :constant),
369
+ entry("Zlib", :gzip, "2.4", receiver_type: :constant),
370
+ entry("Zlib", :gunzip, "2.4", receiver_type: :constant)
371
+ ].freeze
372
+
373
+ BY_METHOD = ENTRIES.group_by(&:method_name).transform_values(&:freeze).freeze
374
+
375
+ module_function
376
+
377
+ def lookup(method_name)
378
+ BY_METHOD[method_name]
379
+ end
380
+ end
381
+ end
382
+ end
383
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+ require "rubocop/lts/ruby/catalog"
5
+
6
+ module RuboCop
7
+ module Cop
8
+ module Lint
9
+ module LtsRuby
10
+ # Reports known Ruby APIs that were introduced after the configured target.
11
+ #
12
+ # RuboCop's TargetRubyVersion handles syntax and version-aware style rules,
13
+ # but it does not provide a complete runtime API compatibility database.
14
+ # This cop fills that gap for explicit receiver calls in the catalog.
15
+ class UnavailableMethod < Base
16
+ MSG = "%<owner>s#%<method>s is unavailable before Ruby %<version>s."
17
+
18
+ RESTRICT_ON_SEND = RuboCop::Lts::Ruby::Catalog::BY_METHOD.keys.freeze
19
+
20
+ def on_send(node)
21
+ return unless node.receiver
22
+
23
+ entries = RuboCop::Lts::Ruby::Catalog.lookup(node.method_name)
24
+ return unless entries
25
+
26
+ applicable_entries = entries.select { |entry| entry_applies?(node, entry) }
27
+ applicable_entries.group_by(&:introduced_in).each_value do |same_version_entries|
28
+ report_unavailable(node, same_version_entries)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def target_ruby_version
35
+ @target_ruby_version ||= Gem::Version.new(super.to_s)
36
+ end
37
+
38
+ def allowed_methods
39
+ Array(cop_config.fetch("AllowedMethods", [])).map(&:to_s)
40
+ end
41
+
42
+ def method_keys(entries)
43
+ entries.map { |entry| "#{entry.owner}##{entry.method_name}" }
44
+ end
45
+
46
+ def entry_applies?(node, entry)
47
+ if entry.receiver_type == :instance
48
+ return false if node.receiver.const_type?
49
+
50
+ return true
51
+ end
52
+
53
+ node.receiver.const_type? && node.receiver.const_name == entry.owner
54
+ end
55
+
56
+ def report_unavailable(node, entries)
57
+ return unless target_ruby_version < entries.first.introduced_in
58
+ return if method_keys(entries).any? { |key| allowed_methods.include?(key) }
59
+
60
+ add_offense(node.selector, message: offense_message(entries))
61
+ end
62
+
63
+ def offense_message(entries)
64
+ owners = entries.map(&:owner).uniq.join("/")
65
+ format(MSG, owner: owners, method: entries.first.method_name, version: entries.first.introduced_in)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end