deprecool 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eca3a279e6790babeed0e659c1093f1b1a9086d9695a637460dda7cfa69d7182
4
- data.tar.gz: a9a735cbd4232f568e002c643370c42491bad699a4ee1cfdd8eaae8e8493c723
3
+ metadata.gz: 354619cb58b3d0b43f1aaffbd90720589a350d8d31f9f52a7c8cab5db4ee8125
4
+ data.tar.gz: b9ae44fa91e1c5c7815902810596f82c115981f35982ac3c1bedc89b78f8d7ce
5
5
  SHA512:
6
- metadata.gz: e8ce7b248608aecf4281f836bedc94c5cc6b493a9581145b22d01a9fee87266a304ce389193841dba4c5e1e5e0029af6fb63397b61e7ca877b4bf19e81b974db
7
- data.tar.gz: 37d39d356843be3105baff39d2f4021b48f9edbf31816b241e715a43f19d489d0b72f3f5abe04de8e4c23749409158c5b5d05470608f3788b618c907faf6c572
6
+ metadata.gz: 84eb84064dfa7230addf8dec40fad61affc9050747f22b3dc2e938e9545a20d58f6d42d567b7c85189d4421d52f57a2a42572eee36fef5624fb46da77a8eee6e
7
+ data.tar.gz: fc44486aab2d15112a99f04136dda227896a728f9e75032fd2450d18471904d60469538fc6714e4350d3621dedeac9770bde1b8d5474e4609da62116ba326f52
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Deprecool
2
2
 
3
+ ## [0.1.4] - 2026-09-20
4
+ ### deprecations
5
+ - Rails 8.2.0 deprecation of ActiveRecord#uniq!
6
+ - Ruby 4.0's deprecation of the limit argument to JSON.dump
7
+
8
+ ### gem internals
9
+ - Finder Test Helper change from `assert_no_offense` to `assert_no_offenses`
10
+ - auto-inclusion of a gem's helper method class to Finders under a gem's namespace
11
+ - support gemspec parsing
12
+ - better file finding and error handling
13
+
3
14
  ## [0.1.3] - 2026-08-24
4
15
  - Added Finder for: "Passing `binds` as a positional argument to `insert` deprecated", see [original commit](https://github.com/rails/rails/commit/2dca5457ab2097626481fdbec233ea56d3fb9ee3)
5
16
  - Added `effort` to the CLI output
data/lib/deprecool/cli.rb CHANGED
@@ -34,38 +34,57 @@ module Deprecool
34
34
  desc 'Scan files or directories for known deprecations'
35
35
 
36
36
  argument :paths, type: :array, desc: 'Files or directories to scan'
37
- option :lockfile, default: 'Gemfile.lock', desc: 'Path to Gemfile.lock, defaults to current directory'
38
- option :gems, type: :array, desc: 'Gems to scan for: --gems=ruby,rails'
39
- option :format, default: 'text', values: %w[text json], desc: 'Output format'
40
- option :all, type: :boolean, default: false, desc: 'Run every finder regardless of version'
41
37
 
42
- def call(paths:, format:, gems: [], all:, lockfile:, **)
38
+ option :format,
39
+ default: 'text',
40
+ values: %w[text json],
41
+ desc: 'Output format'
42
+
43
+ option :gems,
44
+ type: :array,
45
+ desc: 'Gems to scan for: --gems=ruby,rails'
46
+
47
+ option :lockfile,
48
+ default: 'Gemfile.lock',
49
+ desc: 'Path to Gemfile.lock, defaults to current directory'
50
+
51
+ option :all,
52
+ type: :boolean,
53
+ default: false,
54
+ desc: 'Run every finder regardless of version'
55
+
56
+ def call(paths:, format:, gems: [], lockfile:, all:, **)
43
57
  json_output = format == 'json'
44
- files = CLI.ruby_files(paths)
58
+ files = CLI.ruby_files(paths)
45
59
 
46
- if files.empty?
47
- if paths.empty?
48
- warn CLI.colorize('deprecool: please supply a path/to/file/or/directory', :red)
49
- exit 2
50
- end
60
+ if paths.empty?
61
+ puts 'No path given, using \'.\' as the path' unless json_output
62
+ end
51
63
 
52
- warn CLI.colorize("deprecool: no Ruby files found in #{paths.join(', ')}", :red)
53
- exit 2
64
+ # TODO: detect when deprecool is run on too many projects at once
65
+ # i.e. if you run it on a ~/Documents folder and it finds
66
+ # a ton of files from different projects it doesn't know what finders
67
+ # to use and finds nothing
68
+ if files.empty?
69
+ warn "No ruby files found in #{paths.join(', ')}"
70
+ exit 1
54
71
  end
55
72
 
56
- if gems.any?
57
- puts "Scanning for deprecations from: #{gems.join(', ')}" unless json_output
58
- # we won't be scanning a lockfile
59
- gem_versions = []
60
- elsif all
61
- gem_versions = []
62
- gems = []
63
- else
64
- puts "Scanning #{lockfile}..." unless json_output
65
- gem_versions = LockfileParser.parse!(lockfile)
66
-
67
- # we don't need to look for individual gems when scanning gemfiles
68
- gems = []
73
+ dependency_files = CLI.find_dependency_files
74
+ gem_versions = []
75
+ gems ||= []
76
+
77
+ # if the user supplied "all" then we don't worry about finding gem versions
78
+ if !all
79
+ # next we need to find where the dependencies come from, or we could match
80
+ # offenses from not applicable versions of gems
81
+ if dependency_files.any?
82
+ puts "Scanning #{dependency_files.join(', ')} ..."
83
+ gem_versions = DependencyParser.parse_all(dependency_files)
84
+ if gems.any?
85
+ puts "Scanning for deprecations from: #{gems.join(', ')}" unless json_output
86
+ end
87
+ end
69
88
  end
70
89
 
71
90
  finders = Registry.applicable(gems:, gem_versions:, include_all: all)
@@ -92,17 +111,28 @@ module Deprecool
92
111
 
93
112
  def ruby_files(paths)
94
113
  paths = paths.empty? ? ['.'] : paths
114
+ begin
115
+ files = paths.flat_map do |path|
116
+ if File.directory?(path)
117
+ Dir[File.join(path, '**', '*.rb')]
118
+ elsif File.file?(path)
119
+ [path]
120
+ else
121
+ warn "deprecool: no such file or directory: #{path}"
122
+ []
123
+ end
124
+ end.uniq.sort
125
+ rescue SystemCallError => err
126
+ warn "deprecool: An error occurred - #{err}"
127
+ ensure
128
+ files ||= []
95
129
 
96
- paths.flat_map do |path|
97
- if File.directory?(path)
98
- Dir[File.join(path, '**', '*.rb')]
99
- elsif File.file?(path)
100
- [path]
101
- else
102
- warn "deprecool: no such file or directory: #{path}"
103
- []
104
- end
105
- end.uniq.sort
130
+ return files
131
+ end
132
+ end
133
+
134
+ def find_dependency_files
135
+ Dir["*.gemspec", "Gemfile.lock"]
106
136
  end
107
137
 
108
138
  def report(offenses, format, finders)
@@ -116,7 +146,7 @@ module Deprecool
116
146
  def text_report(offenses, finders)
117
147
  if offenses.empty?
118
148
  puts colorize('No deprecations found.', :green)
119
- puts "(#{finders.size} finder(s) active)"
149
+ puts "(#{finders&.size || 0} finder(s) active)"
120
150
  return
121
151
  end
122
152
 
@@ -125,13 +155,13 @@ module Deprecool
125
155
  offenses.each_value do |offense_array|
126
156
  offense = offense_array.first
127
157
 
128
- puts "\n#{colorize(offense.title, :bold)}"
158
+ puts "\n#{offense.gem} #{offense.deprecated_in} - #{colorize(offense.title, :bold)}"
129
159
  puts
130
160
  puts " * #{offense.summary}"
131
161
  puts
132
- puts " #{colorize('effort:', :green).ljust(16)} #{colorize_effort(offense.effort)}"
133
- puts " #{colorize('fix:', :green).ljust(16)} #{offense.suggestion}" if offense.suggestion
134
- puts " #{colorize('source(s):', :green).ljust(16)} #{offense.reference}" if offense.reference
162
+ puts " #{colorize('effort:', :green).ljust(19)} #{colorize_effort(offense.effort)}"
163
+ puts " #{colorize('fix:', :green).ljust(19)} #{offense.suggestion}" if offense.suggestion
164
+ puts " #{colorize('source(s):', :green).ljust(19)} #{offense.reference}" if offense.reference
135
165
  puts ' Found At:'
136
166
  offense_array.each do |o|
137
167
  offense_count += 1
@@ -150,8 +180,8 @@ module Deprecool
150
180
 
151
181
  def list_finders(finders)
152
182
  puts 'Active finders:'
153
- finders.sort_by { [it.gem.to_s, it.deprecated_in.to_s, it.id.to_s] }.each do
154
- puts " - #{it.classname.ljust(35)} (#{it.gem} #{it.deprecated_in.to_s}) — #{it.title}"
183
+ finders.sort_by { |f| [f.gem.to_s, f.deprecated_in.to_s, f.id.to_s] }.each do
184
+ puts " - #{f.classname.ljust(35)} (#{f.gem} #{f.deprecated_in.to_s}) — #{f.title}"
155
185
  end
156
186
  puts ' (none)' if finders.empty?
157
187
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dependency_parsers/gemspec_parser'
4
+ require_relative 'dependency_parsers/lockfile_parser'
5
+ require_relative 'dependency_parsers/null_parser'
6
+
7
+ module Deprecool
8
+ module DependencyParser
9
+ extend self
10
+
11
+ def parse_all(dependency_files)
12
+ files = Array(dependency_files)
13
+
14
+ dependencies = GemVersions.new
15
+
16
+ files.each do |file|
17
+ dependencies.add_all!(parser_for_file(file).parse!(file))
18
+ end
19
+
20
+ dependencies
21
+ end
22
+
23
+ def parser_for_file(dependency_file)
24
+ case File.extname(dependency_file)
25
+ when '.lock' then DependencyParsers::LockfileParser
26
+ when '.gemspec' then DependencyParsers::GemspecParser
27
+ else
28
+ DependencyParsers::NullParser
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deprecool
4
+ module DependencyParsers
5
+ module GemspecParser
6
+ extend self
7
+
8
+ def parse!(gemspec_file_path)
9
+ raise FileNotFound.new('.gemspec') unless File.exist? gemspec_file_path
10
+
11
+ parsed = Gem::Specification.load(gemspec_file_path)
12
+
13
+ raise InvalidGemspec.new(gemspec_file_path) if parsed.nil?
14
+
15
+ gem_versions = GemVersions.new
16
+
17
+ parsed.dependencies.each do |dep|
18
+ gem_versions.add(gem: dep.name, version: minimum_version(dep.requirement))
19
+ end
20
+
21
+ gem_versions.add(gem: 'ruby', version: minimum_version(parsed.required_ruby_version))
22
+
23
+ gem_versions.gem_versions
24
+ end
25
+
26
+ private
27
+
28
+ def minimum_version(requirement)
29
+ lower_bounds = requirement.requirements.filter_map { |req| lower_bound(*req) }
30
+
31
+ lower_bounds.min || Gem::Version.new(0)
32
+ end
33
+
34
+ def lower_bound(operator, version)
35
+ case operator
36
+ when '=', '>=', '>', '~>' then version
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler'
4
+
5
+ module Deprecool
6
+ module DependencyParsers
7
+ module LockfileParser
8
+ extend self
9
+
10
+ def parse!(path_to_gemfile_lock = 'Gemfile.lock', parser: Bundler::LockfileParser)
11
+ raise FileNotFound.new('.lock') unless File.exist? path_to_gemfile_lock
12
+
13
+ parsed = parser.new(File.read(path_to_gemfile_lock))
14
+ lockfile_gem_versions = GemVersions.new
15
+
16
+ parsed.specs.map do |spec|
17
+ lockfile_gem_versions.add(gem: spec.name, version: spec.version)
18
+ end
19
+
20
+ if parsed.ruby_version
21
+ gem, version = parsed.ruby_version.split
22
+ lockfile_gem_versions.add(gem:, version: without_patchlevel(version))
23
+ else
24
+ puts "No Ruby version found"
25
+ end
26
+
27
+ lockfile_gem_versions.gem_versions
28
+ end
29
+
30
+ private
31
+
32
+ def without_patchlevel(version) = version[/\d+(\.\d+)*/]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deprecool
4
+ module DependencyParsers
5
+ module NullParser
6
+ extend self
7
+
8
+ def parse!(file)
9
+ warn "Attempted to parse #{file} for dependencies, but deprecool does not support #{File.extname(file)} yet."
10
+
11
+ []
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # lib/errors.rb
2
+
3
+ module Deprecool
4
+ class Error < StandardError; end
5
+
6
+ class FileNotFound < Error
7
+ def initialize(filetype)
8
+ super("No ruby files found in '#{filetype}'")
9
+ end
10
+ end
11
+
12
+ class InvalidGemspec < Error
13
+ def initialize(path)
14
+ super("Could not load a gem specification from '#{path}'")
15
+ end
16
+ end
17
+
18
+ class NoPathGiven < Error
19
+ def initialize(_msg = nil)
20
+ super("No path given, and no ruby files found under '.'")
21
+ end
22
+ end
23
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'helpers/prism_helpers'
4
-
5
3
  module Deprecool
6
4
  # Base class for every deprecation finder.
7
5
  #
@@ -32,6 +30,14 @@ module Deprecool
32
30
  include PrismHelpers
33
31
 
34
32
  class << self
33
+ # automatically include GemHelpers into the classes for the Gem
34
+ def inherited(subclass)
35
+ gem_name = subclass.name.split("::")[2]
36
+ return if gem_name == "Ruby"
37
+
38
+ subclass.send(:include, const_get("#{gem_name}Helpers"))
39
+ end
40
+
35
41
  # defines class methods that set instance variables
36
42
  %i[gem title summary suggestion reference].each do |attribute|
37
43
  define_method(attribute) do |value = (getter = true)|
@@ -120,9 +126,11 @@ module Deprecool
120
126
 
121
127
  # Record a deprecation at the given node's location.
122
128
  #
123
- # confidence - [:high, :low], but currently the only check for this is
124
- # from the CLI outputting red or yellow depending on confidence
129
+ # confidence - [:high, :low, :none], returns early if :none
130
+ # to save each finder the effort of checking for :none
125
131
  def add_offense(node, confidence: :high)
132
+ return if confidence == :none
133
+
126
134
  location = node.location # a Prism::Location
127
135
 
128
136
  @offenses << Offense.new(
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deprecool
4
+ module Finders
5
+ module Rails
6
+ module V8_2_0
7
+ class ActiveRecordRelationUniq < Deprecool::Finder
8
+ gem :rails
9
+ deprecated_in '8.2.0'
10
+ removed_in '9.0.0'
11
+ title 'ActiveRecord::Relation#uniq! is deprecated'
12
+ summary 'Since Rails 6.1 you have been able to use Relation#uniq! to remove duplicate fields ' \
13
+ 'on a query. This was originally because grouping by the same field twice resulted in ' \
14
+ 'duplicated results. Since Rails 7.0 though, this has been de-duplicated automatically ' \
15
+ 'and this method has been a near no-op'
16
+ suggestion 'Remove the call to #uniq!, don\'t just find and replace though ' \
17
+ 'because `uniq!` is also an Array method. Make sure you have tests ' \
18
+ 'backing these changes, as always.'
19
+ reference 'uniq! added in: https://github.com/rails/rails/pull/39358, ' \
20
+ 'uniq! deprecated in: https://github.com/rails/rails/pull/58525'
21
+ effort :low
22
+
23
+ def on_call_node(node)
24
+ return unless node.name == :uniq!
25
+ return unless args = unwrap_arguments(node.arguments)
26
+
27
+ # neither Array.uniq! or ActiveRecord's uniq! take
28
+ # more than one argument,
29
+ #
30
+ # Array only accepts an optional block
31
+ # AR receives a string or symbol
32
+ return unless args.count == 1
33
+
34
+ confidence = :none
35
+ confidence = :high if [Prism::SymbolNode, Prism::StringNode].include?(args.first.class)
36
+
37
+ # if uniq! is called on a variable like:
38
+ # some_query.uniq!(:somehting)
39
+ # then it's still probably a deprecation, but we are a little less sure
40
+ confidence = :low if receiver_is_bare_variable(node.receiver)
41
+
42
+
43
+ add_offense(node, confidence:)
44
+ end
45
+
46
+ def receiver_is_bare_variable(receiver)
47
+ receiver.is_a?(Prism::CallNode) && receiver.variable_call?
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -9,7 +9,7 @@ module Deprecool
9
9
  deprecated_in '8.2.0'
10
10
  removed_in '9.0.0'
11
11
  title 'Binds being bassed to Insert Update Delete is deprecated'
12
- summary ' Now that `Arel.sql(sql_with_placeholders, *binds)` wraps SQL and its binds' \
12
+ summary ' Now that `Arel.sql(sql_with_placeholders, *binds)` wraps SQL and its binds ' \
13
13
  'together as an `Arel::Nodes::BoundSqlLiteral`, just like ' \
14
14
  '`Model.where("... = ?", value)`, the separate positional is no longer needed'
15
15
  suggestion 'Old: `connection.insert("INSERT INTO topics (title) VALUES (?)", nil, nil, ["hello"])` ' \
@@ -19,11 +19,11 @@ module Deprecool
19
19
 
20
20
  def on_call_node(node)
21
21
  return unless %i[insert update delete].include?(node.name)
22
+ return if node.receiver.nil? # bare `delete :foo, on: :member` in a routes file, etc.
22
23
  return unless arguments = unwrap_arguments(node.arguments)
23
24
  return unless arguments.length > 1
24
25
 
25
26
  confidence = confidence_from_arguments(arguments)
26
- return if confidence == :none
27
27
 
28
28
  add_offense(node, confidence: confidence)
29
29
  end
@@ -38,11 +38,12 @@ module Deprecool
38
38
  # it it's a Prism::CallNode, and not a variable call, then I don't know what it is here,
39
39
  # maybe it's like 'connection.update(generate_sql(some_arg), ["name"])' and if you are doing that
40
40
  # then good luck?
41
- if first.is_a?(Prism::CallNode)
42
- return :low if first.variable_call? # can't tell what's stored in the variable, https://docs.ruby-lang.org/en/master/Prism/CallNode.html#method-i-variable_call-3F
43
- :none
44
- elsif %w[INSERT UPDATE DELETE].include?(first.unescaped[..5].upcase)
45
- :high
41
+ case first
42
+ when Prism::CallNode
43
+ # can't tell what's stored in the variable, https://docs.ruby-lang.org/en/master/Prism/CallNode.html#method-i-variable_call-3F
44
+ first.variable_call? ? :low : :none
45
+ when Prism::StringNode
46
+ first.unescaped.match?(/\A\s*(INSERT|UPDATE|DELETE)\s/i) ? :high : :none
46
47
  else
47
48
  :none
48
49
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deprecool
4
+ module Finders
5
+ module Ruby
6
+ module V4_0_0
7
+ class JsonDumpLimit < Deprecool::Finder
8
+ gem :ruby
9
+ deprecated_in '4.0.0'
10
+ removed_in '4.1.0'
11
+ title 'Passing a positional limit argument to JSON.dump is deprecated'
12
+ summary 'JSON.dump(obj, io, limit) accepted a nesting limit as a positional argument. ' \
13
+ 'Parsing that argument is awful and costly, so it is now named _deprecated_limit ' \
14
+ 'instead of fully removing because updating rdoc is costly.'
15
+ suggestion 'Pass the :max_nesting option instead, e.g. JSON.dump(obj, io, max_nesting: 10)'
16
+ reference 'https://github.com/ruby/json/commit/566bd7c108 ' \
17
+ 'https://github.com/ruby/ruby/commit/d98d7978dc0dd777756ba737295d9cec482f84a3'
18
+ effort :low
19
+
20
+ def on_call_node(node)
21
+ # we only care if the call is JSON.dump
22
+ return unless node.name == :dump
23
+ return unless json_receiver?(node.receiver)
24
+
25
+ # bare keyword options and blocks aren't positional arguments
26
+ positional = positional_args_from(node.arguments)
27
+
28
+ # :high because the receiver is JSON and the limit is only detected when it
29
+ # can't be anything else (a non-nil, non-hash third argument or an integer in place of io)
30
+ add_offense(node, confidence: :high) if passes_limit_arg?(positional)
31
+ end
32
+
33
+ private
34
+
35
+ def json_receiver?(receiver)
36
+ case receiver
37
+ when Prism::ConstantReadNode then receiver.name == :JSON
38
+ # ::JSON.dump
39
+ when Prism::ConstantPathNode then receiver.parent.nil? && receiver.name == :JSON
40
+ else false
41
+ end
42
+ end
43
+
44
+ # follows the same steps JSON.dump uses to decide which argument is the limit
45
+ def passes_limit_arg?(positional)
46
+ # the positional param will dereference into all 3 variables only if
47
+ # all three are actually passed.
48
+ _obj, io, limit = positional
49
+
50
+ # if a third argument is passed, but it's a {hash} or nil
51
+ # then set the third arg to nil in case the second argument is being used
52
+ # as the limit i.e. (JSON.dump(obj, 1, strict: true))
53
+ limit = nil if limit.is_a?(Prism::HashNode) || limit.is_a?(Prism::NilNode)
54
+
55
+ # in the case the third arg was nil or a hash/nil then check if the second arg
56
+ # is an integer, this is similar to what the `dump` method does in ruby 4
57
+ limit = io if limit.nil? && integer?(io)
58
+
59
+
60
+ !limit.nil?
61
+ end
62
+
63
+ def integer?(node) = node.is_a?(Prism::IntegerNode)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -29,7 +29,7 @@ module Deprecool
29
29
  # ObjectSpace or defining '_id2ref' on custom classes
30
30
  confidence = node.receiver.name == :ObjectSpace ? :high : :none
31
31
 
32
- return if confidence == :none
32
+
33
33
 
34
34
  add_offense node, confidence: confidence
35
35
  end
@@ -10,8 +10,9 @@ module Deprecool
10
10
  removed_in '4.1.0'
11
11
  title 'Passing arguments to #to_set is deprecated'
12
12
  summary 'Since Ruby 4.0, passing arguments to Set#to_set / Enumerable#to_set ' \
13
- 'is deprecated and will be removed.'
14
- suggestion 'Call #to_set with no arguments. If you want an instance of a subclass of Set' \
13
+ 'is deprecated and will be removed because it allowed for results that didn\'t make sense. ' \
14
+ 'For example: `enumerable.to_set(StandardError)`'
15
+ suggestion 'Call #to_set with no arguments. If you want an instance of a subclass of Set ' \
15
16
  'from an enumerable construct it explicitly instead: MySetSubclass.new(enumerable)'
16
17
  reference 'https://bugs.ruby-lang.org/issues/21390 https://github.com/ruby/ruby/pull/13489'
17
18
  effort :low
@@ -21,7 +22,7 @@ module Deprecool
21
22
  return unless node.arguments
22
23
 
23
24
  confidence = confidence_from_receiver_node(node.receiver)
24
- return if confidence == :none
25
+
25
26
 
26
27
  add_offense(node, confidence:)
27
28
  end
@@ -0,0 +1,26 @@
1
+ # lib/gem_versions.rb
2
+
3
+ module Deprecool
4
+ class GemVersions
5
+ include Enumerable
6
+
7
+ attr_reader :gem_versions
8
+
9
+ def initialize
10
+ @gem_versions = []
11
+ end
12
+
13
+ def add(gem:, version:)
14
+ @gem_versions << { gem: gem.to_sym,
15
+ version: Gem::Version.new(version.to_s) }
16
+ end
17
+
18
+ def add_all!(gemversions)
19
+ Array(gemversions).each { |gv| add(gem: gv[:gem], version: gv[:version]) }
20
+ end
21
+
22
+ def each(&block)
23
+ @gem_versions.each(&block)
24
+ end
25
+ end
26
+ end
@@ -18,6 +18,12 @@ module PrismHelpers
18
18
  node.arguments
19
19
  end
20
20
 
21
+ def positional_args_from(arguments)
22
+ unwrap_arguments(arguments).to_a.reject do |arg|
23
+ arg.is_a?(Prism::KeywordHashNode) || arg.is_a?(Prism::BlockArgumentNode)
24
+ end
25
+ end
26
+
21
27
  # https://docs.ruby-lang.org/en/master/Prism/ArrayNode.html
22
28
  def unwrap_array(node)
23
29
  return node unless node.is_a?(Prism::ArrayNode)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsHelpers
4
+ def active_record_query_method_names
5
+ # this is as of 8.1.3.1
6
+ # https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html
7
+ %i[
8
+ and annotate create_with distinct
9
+ eager_load excluding extending extract_associated
10
+ from group having in_order_of includes invert_where
11
+ joins left_joins left_outer_joins limit lock
12
+ none offset optimizer_hints or order preload
13
+ readonly references regroup reorder reselect reverse_order rewhere
14
+ select strict_loading structurally_compatible?
15
+ uniq! unscope
16
+ where with with_recursive without
17
+ ]
18
+ end
19
+ end
@@ -22,7 +22,7 @@ module Deprecool
22
22
  def finders_by_gem(gems)
23
23
  targets = []
24
24
  gems.each do |gem|
25
- targets << all_finders.select { it.to_s.match(/#{gem.capitalize}/) }
25
+ targets << all_finders.select { |finder| finder.to_s.match(/#{gem.capitalize}/) }
26
26
  end
27
27
  targets.flatten
28
28
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deprecool
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.4'
5
5
  end
6
6
 
data/lib/deprecool.rb CHANGED
@@ -2,20 +2,23 @@
2
2
 
3
3
  require 'prism'
4
4
 
5
- module Deprecool
6
- class Error < StandardError; end
5
+ module Deprecool; end
6
+
7
+ Dir["#{__dir__}/deprecool/helpers/*.rb"].each do |helper|
8
+ require helper
7
9
  end
8
10
 
11
+ require_relative 'deprecool/errors'
9
12
  require_relative 'deprecool/version'
13
+ require_relative 'deprecool/gem_versions'
10
14
  require_relative 'deprecool/finder'
11
15
  require_relative 'deprecool/registry'
12
16
  require_relative 'deprecool/scanner'
13
- require_relative 'deprecool/lockfile_parser'
17
+ require_relative 'deprecool/dependency_parser'
14
18
  require_relative 'deprecool/cli'
15
19
 
16
20
  # TODO: Scan gemfiles and then only require relevant finders instead of all
17
21
  # of them
18
- Dir[File.join(__dir__, 'deprecool', 'finders', '**', '*.rb')].each do |finder|
22
+ Dir["#{__dir__}/deprecool/finders/**/*.rb"].each do |finder|
19
23
  require finder
20
24
  end
21
-
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deprecool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zac Radford
@@ -54,16 +54,24 @@ files:
54
54
  - exe/deprecool
55
55
  - lib/deprecool.rb
56
56
  - lib/deprecool/cli.rb
57
+ - lib/deprecool/dependency_parser.rb
58
+ - lib/deprecool/dependency_parsers/gemspec_parser.rb
59
+ - lib/deprecool/dependency_parsers/lockfile_parser.rb
60
+ - lib/deprecool/dependency_parsers/null_parser.rb
61
+ - lib/deprecool/errors.rb
57
62
  - lib/deprecool/finder.rb
58
63
  - lib/deprecool/finders/rails/v7.1.0/serializer_positional_class_argument.rb
64
+ - lib/deprecool/finders/rails/v8.2.0/active_record_relation_uniq.rb
59
65
  - lib/deprecool/finders/rails/v8.2.0/no_binds_to_insert_update_delete.rb
60
66
  - lib/deprecool/finders/rails/v8.2.0/redis_cache_store_default_redis_options.rb
61
67
  - lib/deprecool/finders/rails/v8.2.0/to_sql_binds.rb
62
68
  - lib/deprecool/finders/rails/v8.2.0/write_attribute_id.rb
69
+ - lib/deprecool/finders/ruby/v4.0.0/json_dump_limit.rb
63
70
  - lib/deprecool/finders/ruby/v4.0.0/object_space_id2ref.rb
64
71
  - lib/deprecool/finders/ruby/v4.0.0/to_set_arguments.rb
72
+ - lib/deprecool/gem_versions.rb
65
73
  - lib/deprecool/helpers/prism_helpers.rb
66
- - lib/deprecool/lockfile_parser.rb
74
+ - lib/deprecool/helpers/rails_helpers.rb
67
75
  - lib/deprecool/registry.rb
68
76
  - lib/deprecool/scanner.rb
69
77
  - lib/deprecool/version.rb
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler'
4
-
5
- module Deprecool
6
- module LockfileParser
7
- extend self
8
-
9
- def parse!(path_to_gemfile_lock = 'Gemfile.lock', parser: Bundler::LockfileParser)
10
- lockfile = File.read(path_to_gemfile_lock)
11
-
12
- parsed = parser.new(lockfile)
13
-
14
- versions = parsed.specs.map do |spec|
15
- { gem: spec.name, version: spec.version.to_s }
16
- end
17
-
18
- gem, version = parsed.ruby_version.split
19
- versions << { gem:, version: }
20
- end
21
- end
22
- end