Skip to content

Commit

Permalink
Strict type Dependabot::NpmAndYarn::FileUpdater::PackageJsonUpdater (
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMagee authored May 27, 2024
1 parent a219622 commit e925515
Showing 1 changed file with 103 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
# typed: false
# typed: strict
# frozen_string_literal: true

require "sorbet-runtime"

require "dependabot/npm_and_yarn/file_updater"

module Dependabot
module NpmAndYarn
class FileUpdater < Dependabot::FileUpdaters::Base
class PackageJsonUpdater
extend T::Sig

sig do
params(
package_json: Dependabot::DependencyFile,
dependencies: T::Array[Dependabot::Dependency]
) .void
end
def initialize(package_json:, dependencies:)
@package_json = package_json
@dependencies = dependencies
end

sig { returns(Dependabot::DependencyFile) }
def updated_package_json
updated_file = package_json.dup
updated_file.content = updated_package_json_content
Expand All @@ -20,16 +31,20 @@ def updated_package_json

private

sig { returns(Dependabot::DependencyFile) }
attr_reader :package_json

sig { returns(T::Array[Dependabot::Dependency]) }
attr_reader :dependencies

sig { returns(T.nilable(String)) }
def updated_package_json_content
dependencies.reduce(package_json.content.dup) do |content, dep|
updated_requirements(dep).each do |new_req|
updated_requirements(dep)&.each do |new_req|
old_req = old_requirement(dep, new_req)

new_content = update_package_json_declaration(
package_json_content: content,
package_json_content: T.must(content),
dependency_name: dep.name,
old_req: old_req,
new_req: new_req
Expand All @@ -44,7 +59,7 @@ def updated_package_json_content
old_req = old_requirement(dep, new_req)

content = update_package_json_resolutions(
package_json_content: content,
package_json_content: T.must(content),
new_req: new_req,
dependency: dep,
old_req: old_req
Expand All @@ -55,33 +70,52 @@ def updated_package_json_content
end
end

sig do
params(
dependency: Dependabot::Dependency,
new_requirement: T::Hash[Symbol, T.untyped]
)
.returns(T.nilable(T::Hash[Symbol, T.untyped]))
end
def old_requirement(dependency, new_requirement)
dependency.previous_requirements
.select { |r| r[:file] == package_json.name }
.find { |r| r[:groups] == new_requirement[:groups] }
T.must(dependency.previous_requirements)
.select { |r| r[:file] == package_json.name }
.find { |r| r[:groups] == new_requirement[:groups] }
end

sig { params(dependency: Dependabot::Dependency).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def new_requirements(dependency)
dependency.requirements.select { |r| r[:file] == package_json.name }
end

sig { params(dependency: Dependabot::Dependency).returns(T.nilable(T::Array[T::Hash[Symbol, T.untyped]])) }
def updated_requirements(dependency)
return unless dependency.previous_requirements

updated_requirement_pairs =
dependency.requirements.zip(dependency.previous_requirements)
dependency.requirements.zip(T.must(dependency.previous_requirements))
.reject do |new_req, old_req|
next true if new_req == old_req
next false unless old_req[:source].nil?
next false unless old_req&.fetch(:source).nil?

new_req[:requirement] == old_req[:requirement]
new_req[:requirement] == old_req&.fetch(:requirement)
end

updated_requirement_pairs
.map(&:first)
.select { |r| r[:file] == package_json.name }
end

def update_package_json_declaration(package_json_content:, new_req:,
dependency_name:, old_req:)
sig do
params(
package_json_content: String,
new_req: T::Hash[Symbol, T.untyped],
dependency_name: String,
old_req: T.nilable(T::Hash[Symbol, T.untyped])
)
.returns(String)
end
def update_package_json_declaration(package_json_content:, new_req:, dependency_name:, old_req:)
original_line = declaration_line(
dependency_name: dependency_name,
dependency_req: old_req,
Expand All @@ -107,8 +141,16 @@ def update_package_json_declaration(package_json_content:, new_req:,
# For full details on how Yarn resolutions work, see
# https://github.com/yarnpkg/rfcs/blob/master/implemented/
# 0000-selective-versions-resolutions.md
def update_package_json_resolutions(package_json_content:, new_req:,
dependency:, old_req:)
sig do
params(
package_json_content: String,
new_req: T::Hash[Symbol, T.untyped],
dependency: Dependabot::Dependency,
old_req: T.nilable(T::Hash[Symbol, T.untyped])
)
.returns(String)
end
def update_package_json_resolutions(package_json_content:, new_req:, dependency:, old_req:)
dep = dependency
parsed_json_content = JSON.parse(package_json_content)
resolutions =
Expand Down Expand Up @@ -141,31 +183,47 @@ def update_package_json_resolutions(package_json_content:, new_req:,
content
end

sig do
params(
dependency_name: String,
dependency_req: T.nilable(T::Hash[Symbol, T.untyped]),
content: String
)
.returns(String)
end
def declaration_line(dependency_name:, dependency_req:, content:)
git_dependency = dependency_req.dig(:source, :type) == "git"
git_dependency = dependency_req&.dig(:source, :type) == "git"

unless git_dependency
requirement = dependency_req.fetch(:requirement)
requirement = dependency_req&.fetch(:requirement)
return content.match(/"#{Regexp.escape(dependency_name)}"\s*:\s*
"#{Regexp.escape(requirement)}"/x).to_s
end

username, repo =
dependency_req.dig(:source, :url).split("/").last(2)
dependency_req&.dig(:source, :url)&.split("/")&.last(2)

content.match(
%r{"#{Regexp.escape(dependency_name)}"\s*:\s*
".*?#{Regexp.escape(username)}/#{Regexp.escape(repo)}.*"}x
).to_s
end

sig do
params(
original_line: String,
old_req: T.nilable(T::Hash[Symbol, T.untyped]),
new_req: T::Hash[Symbol, T.untyped]
)
.returns(String)
end
def replacement_declaration_line(original_line:, old_req:, new_req:)
was_git_dependency = old_req.dig(:source, :type) == "git"
was_git_dependency = old_req&.dig(:source, :type) == "git"
now_git_dependency = new_req.dig(:source, :type) == "git"

unless was_git_dependency
return original_line.gsub(
%("#{old_req.fetch(:requirement)}"),
%("#{old_req&.fetch(:requirement)}"),
%("#{new_req.fetch(:requirement)}")
)
end
Expand All @@ -186,29 +244,45 @@ def replacement_declaration_line(original_line:, old_req:, new_req:)
end

original_line.gsub(
%(##{old_req.dig(:source, :ref)}"),
%(##{old_req&.dig(:source, :ref)}"),
%(##{new_req.dig(:source, :ref)}")
)
end

sig do
params(
original_line: String,
old_req: T.nilable(T::Hash[Symbol, String]),
new_req: T::Hash[Symbol, String]
)
.returns(String)
end
def update_git_semver_requirement(original_line:, old_req:, new_req:)
if original_line.include?("semver:")
return original_line.gsub(
%(semver:#{old_req.fetch(:requirement)}"),
%(semver:#{old_req&.fetch(:requirement)}"),
%(semver:#{new_req.fetch(:requirement)}")
)
end

raise "Not a semver req!" unless original_line.match?(/#[\^~=<>]/)

original_line.gsub(
%(##{old_req.fetch(:requirement)}"),
%(##{old_req&.fetch(:requirement)}"),
%(##{new_req.fetch(:requirement)}")
)
end

def update_package_json_sections(sections, content, old_line,
new_line)
sig do
params(
sections: T::Array[String],
content: String,
old_line: String,
new_line: String
)
.returns(String)
end
def update_package_json_sections(sections, content, old_line, new_line)
# Currently, Dependabot doesn't update peerDependencies. However,
# if a development dependency is being updated and its requirement
# matches the requirement on a peer dependency we probably want to
Expand All @@ -218,21 +292,21 @@ def update_package_json_sections(sections, content, old_line,
sections += ["peerDependencies"]
sections_regex = /#{sections.join('|')}/

declaration_blocks = []
declaration_blocks = T.let([], T::Array[String])

content.scan(/['"]#{sections_regex}['"]\s*:\s*\{/m) do
mtch = Regexp.last_match
mtch = T.must(Regexp.last_match)
declaration_blocks <<
(mtch.to_s +
mtch.post_match[0..closing_bracket_index(mtch.post_match)])
(mtch.to_s + T.must(mtch.post_match[0..closing_bracket_index(mtch.post_match)]))
end

declaration_blocks.reduce(content.dup) do |new_content, block|
updated_block = block.sub(old_line, new_line)
new_content.sub!(block, updated_block)
new_content.sub(block, updated_block)
end
end

sig { params(string: String).returns(Integer) }
def closing_bracket_index(string)
closes_required = 1

Expand Down

0 comments on commit e925515

Please sign in to comment.