dependabot-cargo 0.325.1 → 0.326.1
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:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 649b2e05f391db12ac0c0a2b684c1307d08c0f70d63cda70fbcf38b684b0ced6
         | 
| 4 | 
            +
              data.tar.gz: 2d99e07be08c7d16413c23a93fa05475aa8958fb32690e8685a9ffa5cd2fc3a0
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 7edc7ef35e787abbc520058bf77514c0ac6837cee2f1bfafe02cac7e5b698d58459f22a7a01f02d5ea00d5515a7506bdf6e64103a2675feac0d4f4841a1d7787
         | 
| 7 | 
            +
              data.tar.gz: 871c65564b60b9d5a3a29a61ec56b86a5b8ddb302b6ac5db11e965fc6eb75272793bb67898ed8fe823ffbf10ff7c9f3fb3dbe361c340cb372145af65becf6f1f
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            # typed:  | 
| 1 | 
            +
            # typed: strict
         | 
| 2 2 | 
             
            # frozen_string_literal: true
         | 
| 3 3 |  | 
| 4 4 | 
             
            require "toml-rb"
         | 
| @@ -25,6 +25,7 @@ module Dependabot | |
| 25 25 | 
             
                  DEPENDENCY_TYPES =
         | 
| 26 26 | 
             
                    %w(dependencies dev-dependencies build-dependencies).freeze
         | 
| 27 27 |  | 
| 28 | 
            +
                  sig { override.returns(T::Array[Dependabot::Dependency]) }
         | 
| 28 29 | 
             
                  def parse
         | 
| 29 30 | 
             
                    check_rust_workspace_root
         | 
| 30 31 |  | 
| @@ -88,10 +89,14 @@ module Dependabot | |
| 88 89 | 
             
                    end, T.nilable(String))
         | 
| 89 90 | 
             
                  end
         | 
| 90 91 |  | 
| 92 | 
            +
                  sig { void }
         | 
| 91 93 | 
             
                  def check_rust_workspace_root
         | 
| 92 94 | 
             
                    cargo_toml = dependency_files.find { |f| f.name == "Cargo.toml" }
         | 
| 93 | 
            -
                    workspace_root = parsed_file( | 
| 94 | 
            -
                    return unless workspace_root
         | 
| 95 | 
            +
                    workspace_root = parsed_file(T.must(cargo_toml))
         | 
| 96 | 
            +
                    return unless workspace_root.is_a?(Hash)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    workspace_config = workspace_root.dig("package", "workspace")
         | 
| 99 | 
            +
                    return unless workspace_config
         | 
| 95 100 |  | 
| 96 101 | 
             
                    msg = "This project is part of a Rust workspace but is not the " \
         | 
| 97 102 | 
             
                          "workspace root." \
         | 
| @@ -106,20 +111,32 @@ module Dependabot | |
| 106 111 | 
             
                  # rubocop:disable Metrics/AbcSize
         | 
| 107 112 | 
             
                  # rubocop:disable Metrics/CyclomaticComplexity
         | 
| 108 113 | 
             
                  # rubocop:disable Metrics/PerceivedComplexity
         | 
| 114 | 
            +
                  sig { returns(DependencySet) }
         | 
| 109 115 | 
             
                  def manifest_dependencies
         | 
| 110 116 | 
             
                    dependency_set = DependencySet.new
         | 
| 111 117 |  | 
| 112 118 | 
             
                    manifest_files.each do |file|
         | 
| 119 | 
            +
                      parsed_content = parsed_file(file)
         | 
| 120 | 
            +
                      next unless parsed_content.is_a?(Hash)
         | 
| 121 | 
            +
             | 
| 113 122 | 
             
                      DEPENDENCY_TYPES.each do |type|
         | 
| 114 | 
            -
                         | 
| 123 | 
            +
                        parsed_content.fetch(type, {}).each do |name, requirement|
         | 
| 124 | 
            +
                          # Skip workspace-inherited dependencies (similar to pnpm catalog)
         | 
| 125 | 
            +
                          # Only skip if workspace is exactly boolean true
         | 
| 126 | 
            +
                          next if requirement.is_a?(Hash) && requirement["workspace"] == true
         | 
| 127 | 
            +
             | 
| 115 128 | 
             
                          next unless name == name_from_declaration(name, requirement)
         | 
| 116 129 | 
             
                          next if lockfile && !version_from_lockfile(name, requirement)
         | 
| 117 130 |  | 
| 118 131 | 
             
                          dependency_set << build_dependency(name, requirement, type, file)
         | 
| 119 132 | 
             
                        end
         | 
| 120 133 |  | 
| 121 | 
            -
                         | 
| 134 | 
            +
                        parsed_content.fetch("target", {}).each do |_, t_details|
         | 
| 122 135 | 
             
                          t_details.fetch(type, {}).each do |name, requirement|
         | 
| 136 | 
            +
                            # Skip workspace-inherited dependencies
         | 
| 137 | 
            +
                            # Only skip if workspace is exactly boolean true
         | 
| 138 | 
            +
                            next if requirement.is_a?(Hash) && requirement["workspace"] == true
         | 
| 139 | 
            +
             | 
| 123 140 | 
             
                            next unless name == name_from_declaration(name, requirement)
         | 
| 124 141 | 
             
                            next if lockfile && !version_from_lockfile(name, requirement)
         | 
| 125 142 |  | 
| @@ -129,7 +146,7 @@ module Dependabot | |
| 129 146 | 
             
                        end
         | 
| 130 147 | 
             
                      end
         | 
| 131 148 |  | 
| 132 | 
            -
                      workspace =  | 
| 149 | 
            +
                      workspace = parsed_content.fetch("workspace", {})
         | 
| 133 150 | 
             
                      workspace.fetch("dependencies", {}).each do |name, requirement|
         | 
| 134 151 | 
             
                        next unless name == name_from_declaration(name, requirement)
         | 
| 135 152 | 
             
                        next if lockfile && !version_from_lockfile(name, requirement)
         | 
| @@ -145,6 +162,10 @@ module Dependabot | |
| 145 162 | 
             
                  # rubocop:enable Metrics/CyclomaticComplexity
         | 
| 146 163 | 
             
                  # rubocop:enable Metrics/PerceivedComplexity
         | 
| 147 164 |  | 
| 165 | 
            +
                  sig do
         | 
| 166 | 
            +
                    params(name: String, requirement: T.any(String, T::Hash[String, String]), type: String,
         | 
| 167 | 
            +
                           file: Dependabot::DependencyFile).returns(Dependency)
         | 
| 168 | 
            +
                  end
         | 
| 148 169 | 
             
                  def build_dependency(name, requirement, type, file)
         | 
| 149 170 | 
             
                    Dependency.new(
         | 
| 150 171 | 
             
                      name: name,
         | 
| @@ -159,11 +180,15 @@ module Dependabot | |
| 159 180 | 
             
                    )
         | 
| 160 181 | 
             
                  end
         | 
| 161 182 |  | 
| 183 | 
            +
                  sig { returns(DependencySet) }
         | 
| 162 184 | 
             
                  def lockfile_dependencies
         | 
| 163 185 | 
             
                    dependency_set = DependencySet.new
         | 
| 164 186 | 
             
                    return dependency_set unless lockfile
         | 
| 165 187 |  | 
| 166 | 
            -
                    parsed_file( | 
| 188 | 
            +
                    lockfile_content = parsed_file(T.must(lockfile))
         | 
| 189 | 
            +
                    return dependency_set unless lockfile_content.is_a?(Hash)
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                    lockfile_content.fetch("package", []).each do |package_details|
         | 
| 167 192 | 
             
                      next unless package_details["source"]
         | 
| 168 193 |  | 
| 169 194 | 
             
                      # TODO: This isn't quite right, as it will only give us one
         | 
| @@ -179,40 +204,49 @@ module Dependabot | |
| 179 204 | 
             
                    dependency_set
         | 
| 180 205 | 
             
                  end
         | 
| 181 206 |  | 
| 207 | 
            +
                  sig { returns(T::Array[String]) }
         | 
| 182 208 | 
             
                  def patched_dependencies
         | 
| 183 209 | 
             
                    root_manifest = manifest_files.find { |f| f.name == "Cargo.toml" }
         | 
| 184 | 
            -
                     | 
| 210 | 
            +
                    parsed_content = parsed_file(T.must(root_manifest))
         | 
| 211 | 
            +
                    return [] unless parsed_content.is_a?(Hash)
         | 
| 212 | 
            +
                    return [] unless parsed_content["patch"]
         | 
| 185 213 |  | 
| 186 | 
            -
                     | 
| 214 | 
            +
                    parsed_content["patch"].values.flat_map(&:keys)
         | 
| 187 215 | 
             
                  end
         | 
| 188 216 |  | 
| 217 | 
            +
                  sig { params(declaration: T.any(String, T::Hash[String, String])).returns(T.nilable(String)) }
         | 
| 189 218 | 
             
                  def requirement_from_declaration(declaration)
         | 
| 190 219 | 
             
                    if declaration.is_a?(String)
         | 
| 191 | 
            -
                       | 
| 192 | 
            -
                     | 
| 193 | 
            -
             | 
| 194 | 
            -
                    return declaration["version"] if declaration["version"].is_a?(String) && declaration["version"] != ""
         | 
| 220 | 
            +
                      declaration == "" ? nil : declaration
         | 
| 221 | 
            +
                    else
         | 
| 222 | 
            +
                      return declaration["version"] if declaration["version"].is_a?(String) && declaration["version"] != ""
         | 
| 195 223 |  | 
| 196 | 
            -
             | 
| 224 | 
            +
                      nil
         | 
| 225 | 
            +
                    end
         | 
| 197 226 | 
             
                  end
         | 
| 198 227 |  | 
| 228 | 
            +
                  sig { params(name: String, declaration: T.any(String, T::Hash[String, String])).returns(String) }
         | 
| 199 229 | 
             
                  def name_from_declaration(name, declaration)
         | 
| 200 | 
            -
                     | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 230 | 
            +
                    if declaration.is_a?(String)
         | 
| 231 | 
            +
                      name
         | 
| 232 | 
            +
                    else
         | 
| 233 | 
            +
                      declaration.fetch("package", name)
         | 
| 234 | 
            +
                    end
         | 
| 204 235 | 
             
                  end
         | 
| 205 236 |  | 
| 237 | 
            +
                  sig { params(declaration: T.any(String, T::Hash[String, String])).returns(T.nilable(T::Hash[Symbol, String])) }
         | 
| 206 238 | 
             
                  def source_from_declaration(declaration)
         | 
| 207 | 
            -
                     | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 239 | 
            +
                    if declaration.is_a?(String)
         | 
| 240 | 
            +
                      nil
         | 
| 241 | 
            +
                    else
         | 
| 242 | 
            +
                      return git_source_details(declaration) if declaration["git"]
         | 
| 243 | 
            +
                      return { type: "path" } if declaration["path"]
         | 
| 212 244 |  | 
| 213 | 
            -
             | 
| 245 | 
            +
                      registry_source_details(declaration)
         | 
| 246 | 
            +
                    end
         | 
| 214 247 | 
             
                  end
         | 
| 215 248 |  | 
| 249 | 
            +
                  sig { params(declaration: T.any(String, T::Hash[String, String])).returns(T.nilable(T::Hash[Symbol, String])) }
         | 
| 216 250 | 
             
                  def registry_source_details(declaration)
         | 
| 217 251 | 
             
                    registry_name = declaration["registry"]
         | 
| 218 252 | 
             
                    return if registry_name.nil?
         | 
| @@ -242,6 +276,7 @@ module Dependabot | |
| 242 276 | 
             
                    end
         | 
| 243 277 | 
             
                  end
         | 
| 244 278 |  | 
| 279 | 
            +
                  sig { params(registry_name: String, index_url: String).returns(T::Hash[Symbol, String]) }
         | 
| 245 280 | 
             
                  def sparse_registry_source_details(registry_name, index_url)
         | 
| 246 281 | 
             
                    token = credentials.find do |cred|
         | 
| 247 282 | 
             
                      cred["type"] == "cargo_registry" && cred["registry"] == registry_name
         | 
| @@ -268,25 +303,35 @@ module Dependabot | |
| 268 303 |  | 
| 269 304 | 
             
                  # Looks up dotted key name in cargo config
         | 
| 270 305 | 
             
                  # e.g. "registries.my_registry.index"
         | 
| 306 | 
            +
                  sig { params(key_name: String).returns(T.nilable(String)) }
         | 
| 271 307 | 
             
                  def cargo_config_field(key_name)
         | 
| 272 308 | 
             
                    cargo_config_from_env(key_name) || cargo_config_from_file(key_name)
         | 
| 273 309 | 
             
                  end
         | 
| 274 310 |  | 
| 311 | 
            +
                  sig { params(key_name: String).returns(T.nilable(String)) }
         | 
| 275 312 | 
             
                  def cargo_config_from_env(key_name)
         | 
| 276 313 | 
             
                    env_var = "CARGO_#{key_name.upcase.tr('-.', '_')}"
         | 
| 277 314 | 
             
                    ENV.fetch(env_var, nil)
         | 
| 278 315 | 
             
                  end
         | 
| 279 316 |  | 
| 317 | 
            +
                  sig { params(key_name: String).returns(T.nilable(String)) }
         | 
| 280 318 | 
             
                  def cargo_config_from_file(key_name)
         | 
| 281 | 
            -
                     | 
| 319 | 
            +
                    config_file = cargo_config
         | 
| 320 | 
            +
                    return nil unless config_file
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                    parsed_file(config_file).dig(*key_name.split("."))
         | 
| 282 323 | 
             
                  end
         | 
| 283 324 |  | 
| 325 | 
            +
                  sig { params(name: String, declaration: T.any(String, T::Hash[String, String])).returns(T.nilable(String)) }
         | 
| 284 326 | 
             
                  def version_from_lockfile(name, declaration)
         | 
| 285 327 | 
             
                    return unless lockfile
         | 
| 286 328 |  | 
| 329 | 
            +
                    lockfile_content = parsed_file(T.must(lockfile))
         | 
| 330 | 
            +
                    return unless lockfile_content.is_a?(Hash)
         | 
| 331 | 
            +
             | 
| 287 332 | 
             
                    candidate_packages =
         | 
| 288 | 
            -
                       | 
| 289 | 
            -
             | 
| 333 | 
            +
                      lockfile_content.fetch("package", [])
         | 
| 334 | 
            +
                                      .select { |p| p["name"] == name }
         | 
| 290 335 |  | 
| 291 336 | 
             
                    if (req = requirement_from_declaration(declaration))
         | 
| 292 337 | 
             
                      req = Cargo::Requirement.new(req)
         | 
| @@ -311,10 +356,12 @@ module Dependabot | |
| 311 356 | 
             
                    version_from_lockfile_details(package)
         | 
| 312 357 | 
             
                  end
         | 
| 313 358 |  | 
| 359 | 
            +
                  sig { params(declaration: T.any(String, T::Hash[String, String])).returns(T::Boolean) }
         | 
| 314 360 | 
             
                  def git_req?(declaration)
         | 
| 315 361 | 
             
                    source_from_declaration(declaration)&.fetch(:type, nil) == "git"
         | 
| 316 362 | 
             
                  end
         | 
| 317 363 |  | 
| 364 | 
            +
                  sig { params(declaration: T.any(String, T::Hash[String, String])).returns(T::Hash[Symbol, String]) }
         | 
| 318 365 | 
             
                  def git_source_details(declaration)
         | 
| 319 366 | 
             
                    {
         | 
| 320 367 | 
             
                      type: "git",
         | 
| @@ -324,38 +371,47 @@ module Dependabot | |
| 324 371 | 
             
                    }
         | 
| 325 372 | 
             
                  end
         | 
| 326 373 |  | 
| 374 | 
            +
                  sig { params(package_details: T::Hash[String, String]).returns(String) }
         | 
| 327 375 | 
             
                  def version_from_lockfile_details(package_details)
         | 
| 328 | 
            -
                    return package_details["version"] unless package_details["source"]&.start_with?("git+")
         | 
| 376 | 
            +
                    return T.must(package_details["version"]) unless package_details["source"]&.start_with?("git+")
         | 
| 329 377 |  | 
| 330 | 
            -
                    package_details["source"].split("#").last
         | 
| 378 | 
            +
                    T.must(T.must(package_details["source"]).split("#").last)
         | 
| 331 379 | 
             
                  end
         | 
| 332 380 |  | 
| 381 | 
            +
                  sig { override.void }
         | 
| 333 382 | 
             
                  def check_required_files
         | 
| 334 383 | 
             
                    raise "No Cargo.toml!" unless get_original_file("Cargo.toml")
         | 
| 335 384 | 
             
                  end
         | 
| 336 385 |  | 
| 386 | 
            +
                  sig { params(file: DependencyFile).returns(T.untyped) }
         | 
| 337 387 | 
             
                  def parsed_file(file)
         | 
| 338 | 
            -
                    @parsed_file ||= {}
         | 
| 388 | 
            +
                    @parsed_file ||= T.let({}, T.untyped)
         | 
| 339 389 | 
             
                    @parsed_file[file.name] ||= TomlRB.parse(file.content)
         | 
| 340 390 | 
             
                  rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
         | 
| 341 391 | 
             
                    raise Dependabot::DependencyFileNotParseable, file.path
         | 
| 342 392 | 
             
                  end
         | 
| 343 393 |  | 
| 394 | 
            +
                  sig { returns(T::Array[Dependabot::DependencyFile]) }
         | 
| 344 395 | 
             
                  def manifest_files
         | 
| 345 | 
            -
                    @manifest_files ||=
         | 
| 396 | 
            +
                    @manifest_files ||= T.let(
         | 
| 346 397 | 
             
                      dependency_files
         | 
| 347 | 
            -
             | 
| 348 | 
            -
             | 
| 398 | 
            +
                              .select { |f| f.name.end_with?("Cargo.toml") }
         | 
| 399 | 
            +
                              .reject(&:support_file?),
         | 
| 400 | 
            +
                      T.nilable(T::Array[Dependabot::DependencyFile])
         | 
| 401 | 
            +
                    )
         | 
| 349 402 | 
             
                  end
         | 
| 350 403 |  | 
| 404 | 
            +
                  sig { returns(T.nilable(Dependabot::DependencyFile)) }
         | 
| 351 405 | 
             
                  def lockfile
         | 
| 352 | 
            -
                    @lockfile ||= get_original_file("Cargo.lock")
         | 
| 406 | 
            +
                    @lockfile ||= T.let(get_original_file("Cargo.lock"), T.nilable(Dependabot::DependencyFile))
         | 
| 353 407 | 
             
                  end
         | 
| 354 408 |  | 
| 409 | 
            +
                  sig { returns(T.nilable(Dependabot::DependencyFile)) }
         | 
| 355 410 | 
             
                  def cargo_config
         | 
| 356 | 
            -
                    @cargo_config ||= get_original_file(".cargo/config.toml")
         | 
| 411 | 
            +
                    @cargo_config ||= T.let(get_original_file(".cargo/config.toml"), T.nilable(Dependabot::DependencyFile))
         | 
| 357 412 | 
             
                  end
         | 
| 358 413 |  | 
| 414 | 
            +
                  sig { returns(T.class_of(Dependabot::Version)) }
         | 
| 359 415 | 
             
                  def version_class
         | 
| 360 416 | 
             
                    Cargo::Version
         | 
| 361 417 | 
             
                  end
         | 
| @@ -0,0 +1,142 @@ | |
| 1 | 
            +
            # typed: strict
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require "sorbet-runtime"
         | 
| 5 | 
            +
            require "dependabot/cargo/file_updater"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Dependabot
         | 
| 8 | 
            +
              module Cargo
         | 
| 9 | 
            +
                class FileUpdater
         | 
| 10 | 
            +
                  class WorkspaceManifestUpdater
         | 
| 11 | 
            +
                    extend T::Sig
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    sig { params(dependencies: T::Array[Dependabot::Dependency], manifest: Dependabot::DependencyFile).void }
         | 
| 14 | 
            +
                    def initialize(dependencies:, manifest:)
         | 
| 15 | 
            +
                      @dependencies = T.let(dependencies, T::Array[Dependabot::Dependency])
         | 
| 16 | 
            +
                      @manifest = T.let(manifest, Dependabot::DependencyFile)
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    sig { returns(String) }
         | 
| 20 | 
            +
                    def updated_manifest_content
         | 
| 21 | 
            +
                      workspace_deps = dependencies.select { |dep| workspace_dependency?(dep) }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      return T.must(manifest.content) if workspace_deps.empty?
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      T.must(workspace_deps.reduce(manifest.content.dup) do |content, dep|
         | 
| 26 | 
            +
                        update_workspace_dependency(T.must(content), dep)
         | 
| 27 | 
            +
                      end)
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    private
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    sig { returns(T::Array[Dependabot::Dependency]) }
         | 
| 33 | 
            +
                    attr_reader :dependencies
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    sig { returns(Dependabot::DependencyFile) }
         | 
| 36 | 
            +
                    attr_reader :manifest
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    sig { params(dep: Dependabot::Dependency).returns(T::Boolean) }
         | 
| 39 | 
            +
                    def workspace_dependency?(dep)
         | 
| 40 | 
            +
                      dep.requirements.any? { |r| r[:groups]&.include?("workspace.dependencies") }
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    sig { params(content: String, dep: Dependabot::Dependency).returns(String) }
         | 
| 44 | 
            +
                    def update_workspace_dependency(content, dep)
         | 
| 45 | 
            +
                      old_req = find_workspace_requirement(dep.previous_requirements)
         | 
| 46 | 
            +
                      new_req = find_workspace_requirement(dep.requirements)
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                      return content if old_req == new_req || !old_req || !new_req
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      # First try to update in the inline [workspace.dependencies] section
         | 
| 51 | 
            +
                      workspace_section_regex = /\[workspace\.dependencies\](.*?)(?=\n\[|\n*\z)/m
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                      updated_content = content.gsub(workspace_section_regex) do |section|
         | 
| 54 | 
            +
                        update_version_in_section(section, dep.name, old_req, new_req)
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      # If content didn't change, try table header notation [workspace.dependencies.name]
         | 
| 58 | 
            +
                      if updated_content == content
         | 
| 59 | 
            +
                        updated_content = update_table_header_notation(content, dep.name, old_req, new_req)
         | 
| 60 | 
            +
                      end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                      updated_content
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    sig { params(requirements: T.nilable(T::Array[T::Hash[Symbol, T.untyped]])).returns(T.nilable(String)) }
         | 
| 66 | 
            +
                    def find_workspace_requirement(requirements)
         | 
| 67 | 
            +
                      requirements&.find { |r| r[:groups]&.include?("workspace.dependencies") }
         | 
| 68 | 
            +
                                  &.fetch(:requirement)
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    sig { params(section: String, dep_name: String, old_req: String, new_req: String).returns(String) }
         | 
| 72 | 
            +
                    def update_version_in_section(section, dep_name, old_req, new_req)
         | 
| 73 | 
            +
                      # Try double-quoted version first
         | 
| 74 | 
            +
                      updated = section.gsub(
         | 
| 75 | 
            +
                        /^(\s*#{Regexp.escape(dep_name)}\s*=\s*)"#{Regexp.escape(old_req)}"/m,
         | 
| 76 | 
            +
                        "\\1\"#{new_req}\""
         | 
| 77 | 
            +
                      )
         | 
| 78 | 
            +
                      return updated if updated != section
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                      # Try single-quoted version
         | 
| 81 | 
            +
                      updated = section.gsub(
         | 
| 82 | 
            +
                        /^(\s*#{Regexp.escape(dep_name)}\s*=\s*)'#{Regexp.escape(old_req)}'/m,
         | 
| 83 | 
            +
                        "\\1'#{new_req}'"
         | 
| 84 | 
            +
                      )
         | 
| 85 | 
            +
                      return updated if updated != section
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      # Try unquoted version
         | 
| 88 | 
            +
                      updated = section.gsub(
         | 
| 89 | 
            +
                        /^(\s*#{Regexp.escape(dep_name)}\s*=\s*)#{Regexp.escape(old_req)}(\s|$)/m,
         | 
| 90 | 
            +
                        "\\1#{new_req}\\2"
         | 
| 91 | 
            +
                      )
         | 
| 92 | 
            +
                      return updated if updated != section
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                      # Try inline table format with double quotes
         | 
| 95 | 
            +
                      updated = section.gsub(
         | 
| 96 | 
            +
                        /^(\s*#{Regexp.escape(dep_name)}\s*=\s*\{[^}]*version\s*=\s*)"#{Regexp.escape(old_req)}"/m,
         | 
| 97 | 
            +
                        "\\1\"#{new_req}\""
         | 
| 98 | 
            +
                      )
         | 
| 99 | 
            +
                      return updated if updated != section
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                      # Try inline table format with single quotes
         | 
| 102 | 
            +
                      section.gsub(
         | 
| 103 | 
            +
                        /^(\s*#{Regexp.escape(dep_name)}\s*=\s*\{[^}]*version\s*=\s*)'#{Regexp.escape(old_req)}'/m,
         | 
| 104 | 
            +
                        "\\1'#{new_req}'"
         | 
| 105 | 
            +
                      )
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    sig { params(content: String, dep_name: String, old_req: String, new_req: String).returns(String) }
         | 
| 109 | 
            +
                    def update_table_header_notation(content, dep_name, old_req, new_req)
         | 
| 110 | 
            +
                      # Match [workspace.dependencies.name] section and its content until next section
         | 
| 111 | 
            +
                      table_header_regex = /\[workspace\.dependencies\.#{Regexp.escape(dep_name)}\](.*?)(?=\n\[|\n*\z)/m
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                      content.gsub(table_header_regex) do |section|
         | 
| 114 | 
            +
                        # Update version = "..." line within this section (double quotes)
         | 
| 115 | 
            +
                        updated = section.gsub(
         | 
| 116 | 
            +
                          /^(\s*version\s*=\s*)"#{Regexp.escape(old_req)}"/m,
         | 
| 117 | 
            +
                          "\\1\"#{new_req}\""
         | 
| 118 | 
            +
                        )
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                        # Try single quotes if double quotes didn't match
         | 
| 121 | 
            +
                        if updated == section
         | 
| 122 | 
            +
                          updated = section.gsub(
         | 
| 123 | 
            +
                            /^(\s*version\s*=\s*)'#{Regexp.escape(old_req)}'/m,
         | 
| 124 | 
            +
                            "\\1'#{new_req}'"
         | 
| 125 | 
            +
                          )
         | 
| 126 | 
            +
                        end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                        # Also try unquoted version
         | 
| 129 | 
            +
                        if updated == section
         | 
| 130 | 
            +
                          updated = section.gsub(
         | 
| 131 | 
            +
                            /^(\s*version\s*=\s*)#{Regexp.escape(old_req)}(\s|$)/m,
         | 
| 132 | 
            +
                            "\\1#{new_req}\\2"
         | 
| 133 | 
            +
                          )
         | 
| 134 | 
            +
                        end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                        updated
         | 
| 137 | 
            +
                      end
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
                  end
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
              end
         | 
| 142 | 
            +
            end
         | 
| @@ -16,6 +16,7 @@ module Dependabot | |
| 16 16 |  | 
| 17 17 | 
             
                  require_relative "file_updater/manifest_updater"
         | 
| 18 18 | 
             
                  require_relative "file_updater/lockfile_updater"
         | 
| 19 | 
            +
                  require_relative "file_updater/workspace_manifest_updater"
         | 
| 19 20 |  | 
| 20 21 | 
             
                  sig { override.returns(T::Array[Regexp]) }
         | 
| 21 22 | 
             
                  def self.updated_files_regex
         | 
| @@ -60,10 +61,18 @@ module Dependabot | |
| 60 61 |  | 
| 61 62 | 
             
                  sig { params(file: Dependabot::DependencyFile).returns(String) }
         | 
| 62 63 | 
             
                  def updated_manifest_content(file)
         | 
| 63 | 
            -
                     | 
| 64 | 
            -
             | 
| 65 | 
            -
                       | 
| 66 | 
            -
             | 
| 64 | 
            +
                    # Use workspace updater for root workspace manifests
         | 
| 65 | 
            +
                    if workspace_root_manifest?(file)
         | 
| 66 | 
            +
                      WorkspaceManifestUpdater.new(
         | 
| 67 | 
            +
                        dependencies: dependencies,
         | 
| 68 | 
            +
                        manifest: file
         | 
| 69 | 
            +
                      ).updated_manifest_content
         | 
| 70 | 
            +
                    else
         | 
| 71 | 
            +
                      ManifestUpdater.new(
         | 
| 72 | 
            +
                        dependencies: dependencies,
         | 
| 73 | 
            +
                        manifest: file
         | 
| 74 | 
            +
                      ).updated_manifest_content
         | 
| 75 | 
            +
                    end
         | 
| 67 76 | 
             
                  end
         | 
| 68 77 |  | 
| 69 78 | 
             
                  sig { returns(String) }
         | 
| @@ -92,6 +101,16 @@ module Dependabot | |
| 92 101 | 
             
                  def lockfile
         | 
| 93 102 | 
             
                    @lockfile ||= T.let(get_original_file("Cargo.lock"), T.nilable(Dependabot::DependencyFile))
         | 
| 94 103 | 
             
                  end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
         | 
| 106 | 
            +
                  def workspace_root_manifest?(file)
         | 
| 107 | 
            +
                    return false unless file.name == "Cargo.toml"
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    parsed_file = TomlRB.parse(file.content)
         | 
| 110 | 
            +
                    parsed_file.key?("workspace") && parsed_file["workspace"].key?("dependencies")
         | 
| 111 | 
            +
                  rescue TomlRB::ParseError
         | 
| 112 | 
            +
                    false
         | 
| 113 | 
            +
                  end
         | 
| 95 114 | 
             
                end
         | 
| 96 115 | 
             
              end
         | 
| 97 116 | 
             
            end
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            # typed:  | 
| 1 | 
            +
            # typed: strict
         | 
| 2 2 | 
             
            # frozen_string_literal: true
         | 
| 3 3 |  | 
| 4 4 | 
             
            require "toml-rb"
         | 
| @@ -12,7 +12,8 @@ require "dependabot/errors" | |
| 12 12 | 
             
            module Dependabot
         | 
| 13 13 | 
             
              module Cargo
         | 
| 14 14 | 
             
                class UpdateChecker
         | 
| 15 | 
            -
                  class VersionResolver
         | 
| 15 | 
            +
                  class VersionResolver # rubocop:disable Metrics/ClassLength
         | 
| 16 | 
            +
                    extend T::Sig
         | 
| 16 17 | 
             
                    UNABLE_TO_UPDATE = /Unable to update (?<url>.*?)$/
         | 
| 17 18 | 
             
                    BRANCH_NOT_FOUND_REGEX = /#{UNABLE_TO_UPDATE}.*to find branch `(?<branch>[^`]+)`/m
         | 
| 18 19 | 
             
                    REVSPEC_PATTERN = /revspec '.*' not found/
         | 
| @@ -26,31 +27,52 @@ module Dependabot | |
| 26 27 | 
             
                    NOT_OUR_REF = /fatal: remote error: upload-pack: not our ref/
         | 
| 27 28 | 
             
                    NOT_OUR_REF_REGEX = /#{NOT_OUR_REF}.*#{UNABLE_TO_UPDATE}/m
         | 
| 28 29 |  | 
| 30 | 
            +
                    sig do
         | 
| 31 | 
            +
                      params(
         | 
| 32 | 
            +
                        dependency: Dependabot::Dependency,
         | 
| 33 | 
            +
                        credentials: T::Array[Dependabot::Credential],
         | 
| 34 | 
            +
                        original_dependency_files: T::Array[Dependabot::DependencyFile],
         | 
| 35 | 
            +
                        prepared_dependency_files: T::Array[Dependabot::DependencyFile]
         | 
| 36 | 
            +
                      ).void
         | 
| 37 | 
            +
                    end
         | 
| 29 38 | 
             
                    def initialize(dependency:, credentials:,
         | 
| 30 39 | 
             
                                   original_dependency_files:, prepared_dependency_files:)
         | 
| 31 40 | 
             
                      @dependency = dependency
         | 
| 32 41 | 
             
                      @prepared_dependency_files = prepared_dependency_files
         | 
| 33 42 | 
             
                      @original_dependency_files = original_dependency_files
         | 
| 34 43 | 
             
                      @credentials = credentials
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      # Initialize instance variables with proper T.let declarations
         | 
| 46 | 
            +
                      @prepared_manifest_files = T.let(nil, T.nilable(T::Array[DependencyFile]))
         | 
| 47 | 
            +
                      @original_manifest_files = T.let(nil, T.nilable(T::Array[DependencyFile]))
         | 
| 35 48 | 
             
                    end
         | 
| 36 49 |  | 
| 50 | 
            +
                    sig { returns(T.nilable(T.any(String, Gem::Version))) }
         | 
| 37 51 | 
             
                    def latest_resolvable_version
         | 
| 38 52 | 
             
                      return @latest_resolvable_version if defined?(@latest_resolvable_version)
         | 
| 39 53 |  | 
| 40 | 
            -
                      @latest_resolvable_version = fetch_latest_resolvable_version
         | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 54 | 
            +
                      @latest_resolvable_version = T.let(fetch_latest_resolvable_version, T.nilable(T.any(String, Gem::Version)))
         | 
| 55 | 
            +
                    rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
         | 
| 56 | 
            +
                      raise Dependabot::DependencyFileNotResolvable, e.message
         | 
| 43 57 | 
             
                    end
         | 
| 44 58 |  | 
| 45 59 | 
             
                    private
         | 
| 46 60 |  | 
| 61 | 
            +
                    sig { returns(Dependency) }
         | 
| 47 62 | 
             
                    attr_reader :dependency
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    sig { returns(T::Array[Credential]) }
         | 
| 48 65 | 
             
                    attr_reader :credentials
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    sig { returns(T::Array[DependencyFile]) }
         | 
| 49 68 | 
             
                    attr_reader :prepared_dependency_files
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    sig { returns(T::Array[DependencyFile]) }
         | 
| 50 71 | 
             
                    attr_reader :original_dependency_files
         | 
| 51 72 |  | 
| 73 | 
            +
                    sig { returns(T.nilable(T.any(String, Gem::Version))) }
         | 
| 52 74 | 
             
                    def fetch_latest_resolvable_version
         | 
| 53 | 
            -
                      base_directory = prepared_dependency_files.first.directory
         | 
| 75 | 
            +
                      base_directory = T.must(prepared_dependency_files.first).directory
         | 
| 54 76 | 
             
                      SharedHelpers.in_a_temporary_directory(base_directory) do
         | 
| 55 77 | 
             
                        write_temporary_dependency_files
         | 
| 56 78 |  | 
| @@ -68,8 +90,10 @@ module Dependabot | |
| 68 90 | 
             
                    rescue SharedHelpers::HelperSubprocessFailed => e
         | 
| 69 91 | 
             
                      retry if better_specification_needed?(e)
         | 
| 70 92 | 
             
                      handle_cargo_errors(e)
         | 
| 93 | 
            +
                      nil
         | 
| 71 94 | 
             
                    end
         | 
| 72 95 |  | 
| 96 | 
            +
                    sig { returns(T.nilable(T.any(String, Gem::Version))) }
         | 
| 73 97 | 
             
                    def fetch_version_from_new_lockfile
         | 
| 74 98 | 
             
                      check_rust_workspace_root unless File.exist?("Cargo.lock")
         | 
| 75 99 | 
             
                      lockfile_content = File.read("Cargo.lock")
         | 
| @@ -95,6 +119,7 @@ module Dependabot | |
| 95 119 | 
             
                    # rubocop:disable Metrics/PerceivedComplexity
         | 
| 96 120 | 
             
                    # rubocop:disable Metrics/CyclomaticComplexity
         | 
| 97 121 | 
             
                    # rubocop:disable Metrics/AbcSize
         | 
| 122 | 
            +
                    sig { params(error: StandardError).returns(T::Boolean) }
         | 
| 98 123 | 
             
                    def better_specification_needed?(error)
         | 
| 99 124 | 
             
                      return false if @custom_specification
         | 
| 100 125 | 
             
                      return false unless error.message.match?(/specification .* is ambigu/)
         | 
| @@ -108,25 +133,26 @@ module Dependabot | |
| 108 133 | 
             
                              dependency.version
         | 
| 109 134 | 
             
                            end
         | 
| 110 135 |  | 
| 111 | 
            -
                      if spec_options.count { |s| s.end_with?(ver) } == 1
         | 
| 112 | 
            -
                        @custom_specification = spec_options.find { |s| s.end_with?(ver) }
         | 
| 136 | 
            +
                      if spec_options.count { |s| s.end_with?(T.must(ver)) } == 1
         | 
| 137 | 
            +
                        @custom_specification = spec_options.find { |s| s.end_with?(T.must(ver)) }
         | 
| 113 138 | 
             
                        return true
         | 
| 114 | 
            -
                      elsif spec_options.count { |s| s.end_with?(ver) } > 1
         | 
| 115 | 
            -
                        spec_options.select! { |s| s.end_with?(ver) }
         | 
| 139 | 
            +
                      elsif spec_options.count { |s| s.end_with?(T.must(ver)) } > 1
         | 
| 140 | 
            +
                        spec_options.select! { |s| s.end_with?(T.must(ver)) }
         | 
| 116 141 | 
             
                      end
         | 
| 117 142 |  | 
| 118 143 | 
             
                      if git_dependency? && git_source_url &&
         | 
| 119 | 
            -
                         spec_options.count { |s| s.include?(git_source_url) } >= 1
         | 
| 120 | 
            -
                        spec_options.select! { |s| s.include?(git_source_url) }
         | 
| 144 | 
            +
                         spec_options.count { |s| s.include?(T.must(git_source_url)) } >= 1
         | 
| 145 | 
            +
                        spec_options.select! { |s| s.include?(T.must(git_source_url)) }
         | 
| 121 146 | 
             
                      end
         | 
| 122 147 |  | 
| 123 | 
            -
                      @custom_specification = spec_options.first
         | 
| 148 | 
            +
                      @custom_specification = T.let(spec_options.first, T.nilable(String))
         | 
| 124 149 | 
             
                      true
         | 
| 125 150 | 
             
                    end
         | 
| 126 151 | 
             
                    # rubocop:enable Metrics/AbcSize
         | 
| 127 152 | 
             
                    # rubocop:enable Metrics/CyclomaticComplexity
         | 
| 128 153 | 
             
                    # rubocop:enable Metrics/PerceivedComplexity
         | 
| 129 154 |  | 
| 155 | 
            +
                    sig { returns(String) }
         | 
| 130 156 | 
             
                    def dependency_spec
         | 
| 131 157 | 
             
                      return @custom_specification if @custom_specification
         | 
| 132 158 |  | 
| @@ -143,6 +169,7 @@ module Dependabot | |
| 143 169 |  | 
| 144 170 | 
             
                    # Shell out to Cargo, which handles everything for us, and does
         | 
| 145 171 | 
             
                    # so without doing an install (so it's fast).
         | 
| 172 | 
            +
                    sig { void }
         | 
| 146 173 | 
             
                    def run_cargo_update_command
         | 
| 147 174 | 
             
                      run_cargo_command(
         | 
| 148 175 | 
             
                        "cargo update -p #{dependency_spec} -vv",
         | 
| @@ -150,6 +177,7 @@ module Dependabot | |
| 150 177 | 
             
                      )
         | 
| 151 178 | 
             
                    end
         | 
| 152 179 |  | 
| 180 | 
            +
                    sig { params(command: String, fingerprint: T.nilable(String)).void }
         | 
| 153 181 | 
             
                    def run_cargo_command(command, fingerprint: nil)
         | 
| 154 182 | 
             
                      start = Time.now
         | 
| 155 183 | 
             
                      command = SharedHelpers.escape_command(command)
         | 
| @@ -176,40 +204,43 @@ module Dependabot | |
| 176 204 | 
             
                      )
         | 
| 177 205 | 
             
                    end
         | 
| 178 206 |  | 
| 207 | 
            +
                    sig { params(prepared: T::Boolean).returns(T.nilable(Integer)) }
         | 
| 179 208 | 
             
                    def write_temporary_dependency_files(prepared: true)
         | 
| 180 209 | 
             
                      write_manifest_files(prepared: prepared)
         | 
| 181 210 |  | 
| 182 | 
            -
                      File.write(lockfile.name, lockfile.content) if lockfile
         | 
| 183 | 
            -
                      File.write(toolchain.name, toolchain.content) if toolchain
         | 
| 211 | 
            +
                      File.write(T.must(lockfile).name, T.must(lockfile).content) if lockfile
         | 
| 212 | 
            +
                      File.write(T.must(toolchain).name, T.must(toolchain).content) if toolchain
         | 
| 184 213 | 
             
                      return unless config
         | 
| 185 214 |  | 
| 186 | 
            -
                      FileUtils.mkdir_p(File.dirname(config.name))
         | 
| 187 | 
            -
                      File.write(config.name, config.content)
         | 
| 215 | 
            +
                      FileUtils.mkdir_p(File.dirname(T.must(config).name))
         | 
| 216 | 
            +
                      File.write(T.must(config).name, T.must(config).content)
         | 
| 188 217 | 
             
                    end
         | 
| 189 218 |  | 
| 219 | 
            +
                    sig { void }
         | 
| 190 220 | 
             
                    def check_rust_workspace_root
         | 
| 191 221 | 
             
                      cargo_toml = original_dependency_files
         | 
| 192 222 | 
             
                                   .select { |f| f.name.end_with?("../Cargo.toml") }
         | 
| 193 223 | 
             
                                   .max_by { |f| f.name.length }
         | 
| 194 | 
            -
                      return unless TomlRB.parse(cargo_toml.content)["workspace"]
         | 
| 224 | 
            +
                      return unless TomlRB.parse(T.must(cargo_toml).content)["workspace"]
         | 
| 195 225 |  | 
| 196 226 | 
             
                      msg = "This project is part of a Rust workspace but is not the " \
         | 
| 197 227 | 
             
                            "workspace root." \
         | 
| 198 228 |  | 
| 199 | 
            -
                      if cargo_toml.directory != "/"
         | 
| 229 | 
            +
                      if T.must(cargo_toml).directory != "/"
         | 
| 200 230 | 
             
                        msg += "Please update your settings so Dependabot points at the " \
         | 
| 201 | 
            -
                               "workspace root instead of #{cargo_toml.directory}."
         | 
| 231 | 
            +
                               "workspace root instead of #{T.must(cargo_toml).directory}."
         | 
| 202 232 | 
             
                      end
         | 
| 203 233 | 
             
                      raise Dependabot::DependencyFileNotResolvable, msg
         | 
| 204 234 | 
             
                    end
         | 
| 205 235 |  | 
| 206 236 | 
             
                    # rubocop:disable Metrics/AbcSize
         | 
| 207 237 | 
             
                    # rubocop:disable Metrics/PerceivedComplexity
         | 
| 238 | 
            +
                    sig { params(error: StandardError).void }
         | 
| 208 239 | 
             
                    def handle_cargo_errors(error)
         | 
| 209 240 | 
             
                      if error.message.include?("does not have these features")
         | 
| 210 241 | 
             
                        # TODO: Ideally we should update the declaration not to ask
         | 
| 211 242 | 
             
                        # for the specified features
         | 
| 212 | 
            -
                        return | 
| 243 | 
            +
                        return
         | 
| 213 244 | 
             
                      end
         | 
| 214 245 |  | 
| 215 246 | 
             
                      if error.message.include?("authenticate when downloading repo") ||
         | 
| @@ -218,22 +249,24 @@ module Dependabot | |
| 218 249 | 
             
                        # consistent error)
         | 
| 219 250 | 
             
                        urls = unreachable_git_urls
         | 
| 220 251 |  | 
| 221 | 
            -
                        if urls.none?
         | 
| 222 | 
            -
                          url = error.message.match(UNABLE_TO_UPDATE)
         | 
| 223 | 
            -
             | 
| 224 | 
            -
                          raise if reachable_git_urls.include?(url)
         | 
| 252 | 
            +
                        if T.must(urls).none?
         | 
| 253 | 
            +
                          url = T.must(T.must(error.message.match(UNABLE_TO_UPDATE))
         | 
| 254 | 
            +
                                        .named_captures.fetch("url")).split(/[#?]/).first
         | 
| 255 | 
            +
                          raise if T.must(reachable_git_urls).include?(url)
         | 
| 225 256 |  | 
| 226 | 
            -
                           | 
| 257 | 
            +
                          # Fix: Wrap url in T.must since split().first can return nil
         | 
| 258 | 
            +
                          T.must(urls) << T.must(url)
         | 
| 227 259 | 
             
                        end
         | 
| 228 260 |  | 
| 229 | 
            -
                        raise Dependabot::GitDependenciesNotReachable, urls
         | 
| 261 | 
            +
                        raise Dependabot::GitDependenciesNotReachable, T.must(urls)
         | 
| 230 262 | 
             
                      end
         | 
| 231 263 |  | 
| 232 264 | 
             
                      [BRANCH_NOT_FOUND_REGEX, REF_NOT_FOUND_REGEX, GIT_REF_NOT_FOUND_REGEX, NOT_OUR_REF_REGEX].each do |regex|
         | 
| 233 265 | 
             
                        next unless error.message.match?(regex)
         | 
| 234 266 |  | 
| 235 | 
            -
                        dependency_url = error.message.match(regex).named_captures.fetch("url").split(/[#?]/).first
         | 
| 236 | 
            -
                         | 
| 267 | 
            +
                        dependency_url = T.must(T.must(error.message.match(regex)).named_captures.fetch("url")).split(/[#?]/).first
         | 
| 268 | 
            +
                        # Fix: Wrap dependency_url in T.must since split().first can return nil
         | 
| 269 | 
            +
                        raise Dependabot::GitDependencyReferenceNotFound, T.must(dependency_url)
         | 
| 237 270 | 
             
                      end
         | 
| 238 271 |  | 
| 239 272 | 
             
                      if workspace_native_library_update_error?(error.message)
         | 
| @@ -268,8 +301,9 @@ module Dependabot | |
| 268 301 | 
             
                    # rubocop:enable Metrics/AbcSize
         | 
| 269 302 | 
             
                    # rubocop:enable Metrics/PerceivedComplexity
         | 
| 270 303 |  | 
| 304 | 
            +
                    sig { params(message: T.nilable(String)).returns(T.any(Dependabot::Version, T::Boolean)) }
         | 
| 271 305 | 
             
                    def using_old_toolchain?(message)
         | 
| 272 | 
            -
                      return true if message.include?("usage of sparse registries requires `-Z sparse-registry`")
         | 
| 306 | 
            +
                      return true if T.must(message).include?("usage of sparse registries requires `-Z sparse-registry`")
         | 
| 273 307 |  | 
| 274 308 | 
             
                      version_log = /rust version (?<version>\d.\d+)/.match(message)
         | 
| 275 309 | 
             
                      return false unless version_log
         | 
| @@ -277,11 +311,12 @@ module Dependabot | |
| 277 311 | 
             
                      version_class.new(version_log[:version]) < version_class.new("1.68")
         | 
| 278 312 | 
             
                    end
         | 
| 279 313 |  | 
| 314 | 
            +
                    sig { returns(T.nilable(T::Array[String])) }
         | 
| 280 315 | 
             
                    def unreachable_git_urls
         | 
| 281 316 | 
             
                      return @unreachable_git_urls if defined?(@unreachable_git_urls)
         | 
| 282 317 |  | 
| 283 | 
            -
                      @unreachable_git_urls = []
         | 
| 284 | 
            -
                      @reachable_git_urls = []
         | 
| 318 | 
            +
                      @unreachable_git_urls = T.let([], T.nilable(T::Array[String]))
         | 
| 319 | 
            +
                      @reachable_git_urls = T.let([], T.nilable(T::Array[String]))
         | 
| 285 320 |  | 
| 286 321 | 
             
                      dependencies = FileParser.new(
         | 
| 287 322 | 
             
                        dependency_files: original_dependency_files,
         | 
| @@ -295,19 +330,20 @@ module Dependabot | |
| 295 330 | 
             
                        )
         | 
| 296 331 | 
             
                        next unless checker.git_dependency?
         | 
| 297 332 |  | 
| 298 | 
            -
                        url = dep.requirements.find { |r| r.dig(:source, :type) == "git" }
         | 
| 299 | 
            -
             | 
| 333 | 
            +
                        url = T.must(dep.requirements.find { |r| r.dig(:source, :type) == "git" })
         | 
| 334 | 
            +
                               .fetch(:source).fetch(:url)
         | 
| 300 335 |  | 
| 301 336 | 
             
                        if checker.git_repo_reachable?
         | 
| 302 | 
            -
                          @reachable_git_urls << url
         | 
| 337 | 
            +
                          T.must(@reachable_git_urls) << url
         | 
| 303 338 | 
             
                        else
         | 
| 304 | 
            -
                          @unreachable_git_urls << url
         | 
| 339 | 
            +
                          T.must(@unreachable_git_urls) << url
         | 
| 305 340 | 
             
                        end
         | 
| 306 341 | 
             
                      end
         | 
| 307 342 |  | 
| 308 343 | 
             
                      @unreachable_git_urls
         | 
| 309 344 | 
             
                    end
         | 
| 310 345 |  | 
| 346 | 
            +
                    sig { returns(T.nilable(T::Array[String])) }
         | 
| 311 347 | 
             
                    def reachable_git_urls
         | 
| 312 348 | 
             
                      return @reachable_git_urls if defined?(@reachable_git_urls)
         | 
| 313 349 |  | 
| @@ -315,6 +351,7 @@ module Dependabot | |
| 315 351 | 
             
                      @reachable_git_urls
         | 
| 316 352 | 
             
                    end
         | 
| 317 353 |  | 
| 354 | 
            +
                    sig { params(message: String).returns(T::Boolean) }
         | 
| 318 355 | 
             
                    def resolvability_error?(message)
         | 
| 319 356 | 
             
                      return true if message.include?("failed to parse lock")
         | 
| 320 357 | 
             
                      return true if message.include?("believes it's in a workspace")
         | 
| @@ -330,8 +367,9 @@ module Dependabot | |
| 330 367 | 
             
                      !original_requirements_resolvable
         | 
| 331 368 | 
             
                    end
         | 
| 332 369 |  | 
| 370 | 
            +
                    sig { returns(T.any(TrueClass, FalseClass, Symbol)) }
         | 
| 333 371 | 
             
                    def original_requirements_resolvable?
         | 
| 334 | 
            -
                      base_directory = original_dependency_files.first.directory
         | 
| 372 | 
            +
                      base_directory = T.must(original_dependency_files.first).directory
         | 
| 335 373 | 
             
                      SharedHelpers.in_a_temporary_directory(base_directory) do
         | 
| 336 374 | 
             
                        write_temporary_dependency_files(prepared: false)
         | 
| 337 375 |  | 
| @@ -353,10 +391,11 @@ module Dependabot | |
| 353 391 | 
             
                      end
         | 
| 354 392 | 
             
                    end
         | 
| 355 393 |  | 
| 394 | 
            +
                    sig { params(message: String).returns(T::Boolean) }
         | 
| 356 395 | 
             
                    def workspace_native_library_update_error?(message)
         | 
| 357 396 | 
             
                      return false unless message.include?("native library")
         | 
| 358 397 |  | 
| 359 | 
            -
                      library_count = prepared_manifest_files.count do |file|
         | 
| 398 | 
            +
                      library_count = T.must(prepared_manifest_files).count do |file|
         | 
| 360 399 | 
             
                        package_name = TomlRB.parse(file.content).dig("package", "name")
         | 
| 361 400 | 
             
                        next false unless package_name
         | 
| 362 401 |  | 
| @@ -366,17 +405,18 @@ module Dependabot | |
| 366 405 | 
             
                      library_count >= 2
         | 
| 367 406 | 
             
                    end
         | 
| 368 407 |  | 
| 408 | 
            +
                    sig { params(prepared: T::Boolean).returns(T.nilable(T::Array[Dependabot::DependencyFile])) }
         | 
| 369 409 | 
             
                    def write_manifest_files(prepared: true)
         | 
| 370 410 | 
             
                      manifest_files = if prepared then prepared_manifest_files
         | 
| 371 411 | 
             
                                       else
         | 
| 372 412 | 
             
                                         original_manifest_files
         | 
| 373 413 | 
             
                                       end
         | 
| 374 414 |  | 
| 375 | 
            -
                      manifest_files.each do |file|
         | 
| 415 | 
            +
                      T.must(manifest_files).each do |file|
         | 
| 376 416 | 
             
                        path = file.name
         | 
| 377 417 | 
             
                        dir = Pathname.new(path).dirname
         | 
| 378 418 | 
             
                        FileUtils.mkdir_p(dir)
         | 
| 379 | 
            -
                        File.write(file.name, sanitized_manifest_content(file.content))
         | 
| 419 | 
            +
                        File.write(file.name, sanitized_manifest_content(T.must(file.content)))
         | 
| 380 420 |  | 
| 381 421 | 
             
                        next if virtual_manifest?(file)
         | 
| 382 422 |  | 
| @@ -388,26 +428,30 @@ module Dependabot | |
| 388 428 | 
             
                      end
         | 
| 389 429 | 
             
                    end
         | 
| 390 430 |  | 
| 431 | 
            +
                    sig { returns(T.nilable(String)) }
         | 
| 391 432 | 
             
                    def git_dependency_version
         | 
| 392 433 | 
             
                      return unless lockfile
         | 
| 393 434 |  | 
| 394 | 
            -
                      TomlRB.parse(lockfile.content)
         | 
| 435 | 
            +
                      TomlRB.parse(T.must(lockfile).content)
         | 
| 395 436 | 
             
                            .fetch("package", [])
         | 
| 396 437 | 
             
                            .select { |p| p["name"] == dependency.name }
         | 
| 397 438 | 
             
                            .find { |p| p["source"].end_with?(dependency.version) }
         | 
| 398 439 | 
             
                            .fetch("version")
         | 
| 399 440 | 
             
                    end
         | 
| 400 441 |  | 
| 442 | 
            +
                    sig { returns(T.nilable(String)) }
         | 
| 401 443 | 
             
                    def git_source_url
         | 
| 402 444 | 
             
                      dependency.requirements
         | 
| 403 445 | 
             
                                .find { |r| r.dig(:source, :type) == "git" }
         | 
| 404 446 | 
             
                                &.dig(:source, :url)
         | 
| 405 447 | 
             
                    end
         | 
| 406 448 |  | 
| 449 | 
            +
                    sig { returns(String) }
         | 
| 407 450 | 
             
                    def dummy_app_content
         | 
| 408 451 | 
             
                      %{fn main() {\nprintln!("Hello, world!");\n}}
         | 
| 409 452 | 
             
                    end
         | 
| 410 453 |  | 
| 454 | 
            +
                    sig { params(content: String).returns(String) }
         | 
| 411 455 | 
             
                    def sanitized_manifest_content(content)
         | 
| 412 456 | 
             
                      object = TomlRB.parse(content)
         | 
| 413 457 |  | 
| @@ -424,32 +468,40 @@ module Dependabot | |
| 424 468 | 
             
                      TomlRB.dump(object)
         | 
| 425 469 | 
             
                    end
         | 
| 426 470 |  | 
| 471 | 
            +
                    sig { returns(T.nilable(T::Array[DependencyFile])) }
         | 
| 427 472 | 
             
                    def prepared_manifest_files
         | 
| 428 473 | 
             
                      @prepared_manifest_files ||=
         | 
| 429 474 | 
             
                        prepared_dependency_files
         | 
| 430 475 | 
             
                        .select { |f| f.name.end_with?("Cargo.toml") }
         | 
| 431 476 | 
             
                    end
         | 
| 432 477 |  | 
| 478 | 
            +
                    sig { returns(T.nilable(T::Array[DependencyFile])) }
         | 
| 433 479 | 
             
                    def original_manifest_files
         | 
| 434 480 | 
             
                      @original_manifest_files ||=
         | 
| 435 481 | 
             
                        original_dependency_files
         | 
| 436 482 | 
             
                        .select { |f| f.name.end_with?("Cargo.toml") }
         | 
| 437 483 | 
             
                    end
         | 
| 438 484 |  | 
| 485 | 
            +
                    sig { returns(T.nilable(DependencyFile)) }
         | 
| 439 486 | 
             
                    def lockfile
         | 
| 440 | 
            -
                      @lockfile ||= prepared_dependency_files
         | 
| 441 | 
            -
             | 
| 487 | 
            +
                      @lockfile ||= T.let(prepared_dependency_files
         | 
| 488 | 
            +
                                            .find { |f| f.name == "Cargo.lock" }, T.nilable(Dependabot::DependencyFile))
         | 
| 442 489 | 
             
                    end
         | 
| 443 490 |  | 
| 491 | 
            +
                    sig { returns(T.nilable(DependencyFile)) }
         | 
| 444 492 | 
             
                    def toolchain
         | 
| 445 | 
            -
                      @toolchain ||= original_dependency_files
         | 
| 446 | 
            -
             | 
| 493 | 
            +
                      @toolchain ||= T.let(original_dependency_files
         | 
| 494 | 
            +
                                             .find { |f| f.name == "rust-toolchain" }, T.nilable(Dependabot::DependencyFile))
         | 
| 447 495 | 
             
                    end
         | 
| 448 496 |  | 
| 497 | 
            +
                    sig { returns(T.nilable(DependencyFile)) }
         | 
| 449 498 | 
             
                    def config
         | 
| 450 | 
            -
                      @config ||= original_dependency_files.find  | 
| 499 | 
            +
                      @config ||= T.let(original_dependency_files.find do |f|
         | 
| 500 | 
            +
                        f.name == ".cargo/config.toml"
         | 
| 501 | 
            +
                      end, T.nilable(Dependabot::DependencyFile))
         | 
| 451 502 | 
             
                    end
         | 
| 452 503 |  | 
| 504 | 
            +
                    sig { returns(T::Boolean) }
         | 
| 453 505 | 
             
                    def git_dependency?
         | 
| 454 506 | 
             
                      GitCommitChecker.new(
         | 
| 455 507 | 
             
                        dependency: dependency,
         | 
| @@ -460,10 +512,12 @@ module Dependabot | |
| 460 512 | 
             
                    # When the package table is not present in a workspace manifest, it is
         | 
| 461 513 | 
             
                    # called a virtual manifest: https://doc.rust-lang.org/cargo/reference/
         | 
| 462 514 | 
             
                    # manifest.html#virtual-manifest
         | 
| 515 | 
            +
                    sig { params(file: DependencyFile).returns(T::Boolean) }
         | 
| 463 516 | 
             
                    def virtual_manifest?(file)
         | 
| 464 | 
            -
                      !file.content.include?("[package]")
         | 
| 517 | 
            +
                      !T.must(file.content).include?("[package]")
         | 
| 465 518 | 
             
                    end
         | 
| 466 519 |  | 
| 520 | 
            +
                    sig { returns(T.class_of(Dependabot::Version)) }
         | 
| 467 521 | 
             
                    def version_class
         | 
| 468 522 | 
             
                      dependency.version_class
         | 
| 469 523 | 
             
                    end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: dependabot-cargo
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.326.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Dependabot
         | 
| @@ -15,14 +15,14 @@ dependencies: | |
| 15 15 | 
             
                requirements:
         | 
| 16 16 | 
             
                - - '='
         | 
| 17 17 | 
             
                  - !ruby/object:Gem::Version
         | 
| 18 | 
            -
                    version: 0. | 
| 18 | 
            +
                    version: 0.326.1
         | 
| 19 19 | 
             
              type: :runtime
         | 
| 20 20 | 
             
              prerelease: false
         | 
| 21 21 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 22 22 | 
             
                requirements:
         | 
| 23 23 | 
             
                - - '='
         | 
| 24 24 | 
             
                  - !ruby/object:Gem::Version
         | 
| 25 | 
            -
                    version: 0. | 
| 25 | 
            +
                    version: 0.326.1
         | 
| 26 26 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 27 27 | 
             
              name: debug
         | 
| 28 28 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -247,6 +247,7 @@ files: | |
| 247 247 | 
             
            - lib/dependabot/cargo/file_updater.rb
         | 
| 248 248 | 
             
            - lib/dependabot/cargo/file_updater/lockfile_updater.rb
         | 
| 249 249 | 
             
            - lib/dependabot/cargo/file_updater/manifest_updater.rb
         | 
| 250 | 
            +
            - lib/dependabot/cargo/file_updater/workspace_manifest_updater.rb
         | 
| 250 251 | 
             
            - lib/dependabot/cargo/helpers.rb
         | 
| 251 252 | 
             
            - lib/dependabot/cargo/language.rb
         | 
| 252 253 | 
             
            - lib/dependabot/cargo/metadata_finder.rb
         | 
| @@ -265,7 +266,7 @@ licenses: | |
| 265 266 | 
             
            - MIT
         | 
| 266 267 | 
             
            metadata:
         | 
| 267 268 | 
             
              bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
         | 
| 268 | 
            -
              changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0. | 
| 269 | 
            +
              changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.326.1
         | 
| 269 270 | 
             
            rdoc_options: []
         | 
| 270 271 | 
             
            require_paths:
         | 
| 271 272 | 
             
            - lib
         |