dependabot-nuget 0.245.0 → 0.247.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +42 -7
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +164 -90
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +38 -2
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +92 -18
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +27 -0
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +115 -14
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/{UpdateWorker.DirsProj.cs → UpdateWorkerTests.DirsProj.cs} +22 -24
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +66 -0
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +373 -83
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +117 -4
 - data/lib/dependabot/nuget/cache_manager.rb +9 -3
 - data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +15 -12
 - data/lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb +13 -3
 - data/lib/dependabot/nuget/file_fetcher.rb +79 -31
 - data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +10 -2
 - data/lib/dependabot/nuget/file_parser/global_json_parser.rb +10 -2
 - data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +11 -2
 - data/lib/dependabot/nuget/file_parser/project_file_parser.rb +140 -45
 - data/lib/dependabot/nuget/file_parser/property_value_finder.rb +57 -5
 - data/lib/dependabot/nuget/file_parser.rb +18 -4
 - data/lib/dependabot/nuget/file_updater/property_value_updater.rb +25 -8
 - data/lib/dependabot/nuget/file_updater.rb +74 -38
 - data/lib/dependabot/nuget/http_response_helpers.rb +19 -0
 - data/lib/dependabot/nuget/metadata_finder.rb +32 -4
 - data/lib/dependabot/nuget/nuget_client.rb +31 -13
 - data/lib/dependabot/nuget/requirement.rb +4 -1
 - data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +26 -15
 - data/lib/dependabot/nuget/update_checker/dependency_finder.rb +23 -13
 - data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +83 -21
 - data/lib/dependabot/nuget/update_checker/repository_finder.rb +29 -13
 - data/lib/dependabot/nuget/update_checker/tfm_finder.rb +2 -2
 - data/lib/dependabot/nuget/update_checker/version_finder.rb +15 -6
 - data/lib/dependabot/nuget/update_checker.rb +6 -7
 - data/lib/dependabot/nuget/version.rb +7 -2
 - metadata +21 -7
 - data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +0 -317
 
| 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # typed:  
     | 
| 
      
 1 
     | 
    
         
            +
            # typed: strong
         
     | 
| 
       2 
2 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            require "nokogiri"
         
     | 
| 
         @@ -30,7 +30,21 @@ module Dependabot 
     | 
|
| 
       30 
30 
     | 
    
         
             
                    dependency_set += packages_config_dependencies
         
     | 
| 
       31 
31 
     | 
    
         
             
                    dependency_set += global_json_dependencies if global_json
         
     | 
| 
       32 
32 
     | 
    
         
             
                    dependency_set += dotnet_tools_json_dependencies if dotnet_tools_json
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    (dependencies, deps_with_unresolved_versions) = dependency_set.dependencies.partition do |d|
         
     | 
| 
      
 35 
     | 
    
         
            +
                      # try to parse the version; don't care about result, just that it succeeded
         
     | 
| 
      
 36 
     | 
    
         
            +
                      _ = Version.new(d.version)
         
     | 
| 
      
 37 
     | 
    
         
            +
                      true
         
     | 
| 
      
 38 
     | 
    
         
            +
                    rescue ArgumentError
         
     | 
| 
      
 39 
     | 
    
         
            +
                      # version could not be parsed
         
     | 
| 
      
 40 
     | 
    
         
            +
                      false
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    deps_with_unresolved_versions.each do |d|
         
     | 
| 
      
 44 
     | 
    
         
            +
                      Dependabot.logger.warn "Dependency '#{d.name}' excluded due to unparsable version: #{d.version}"
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    dependencies
         
     | 
| 
       34 
48 
     | 
    
         
             
                  end
         
     | 
| 
       35 
49 
     | 
    
         | 
| 
       36 
50 
     | 
    
         
             
                  private
         
     | 
| 
         @@ -63,14 +77,14 @@ module Dependabot 
     | 
|
| 
       63 
77 
     | 
    
         
             
                  def global_json_dependencies
         
     | 
| 
       64 
78 
     | 
    
         
             
                    return DependencySet.new unless global_json
         
     | 
| 
       65 
79 
     | 
    
         | 
| 
       66 
     | 
    
         
            -
                    GlobalJsonParser.new(global_json: global_json).dependency_set
         
     | 
| 
      
 80 
     | 
    
         
            +
                    GlobalJsonParser.new(global_json: T.must(global_json)).dependency_set
         
     | 
| 
       67 
81 
     | 
    
         
             
                  end
         
     | 
| 
       68 
82 
     | 
    
         | 
| 
       69 
83 
     | 
    
         
             
                  sig { returns(Dependabot::FileParsers::Base::DependencySet) }
         
     | 
| 
       70 
84 
     | 
    
         
             
                  def dotnet_tools_json_dependencies
         
     | 
| 
       71 
85 
     | 
    
         
             
                    return DependencySet.new unless dotnet_tools_json
         
     | 
| 
       72 
86 
     | 
    
         | 
| 
       73 
     | 
    
         
            -
                    DotNetToolsJsonParser.new(dotnet_tools_json: dotnet_tools_json).dependency_set
         
     | 
| 
      
 87 
     | 
    
         
            +
                    DotNetToolsJsonParser.new(dotnet_tools_json: T.must(dotnet_tools_json)).dependency_set
         
     | 
| 
       74 
88 
     | 
    
         
             
                  end
         
     | 
| 
       75 
89 
     | 
    
         | 
| 
       76 
90 
     | 
    
         
             
                  sig { returns(Dependabot::Nuget::FileParser::ProjectFileParser) }
         
     | 
| 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # typed:  
     | 
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
       2 
2 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            require "nokogiri"
         
     | 
| 
         @@ -11,25 +11,38 @@ module Dependabot 
     | 
|
| 
       11 
11 
     | 
    
         
             
              module Nuget
         
     | 
| 
       12 
12 
     | 
    
         
             
                class FileUpdater
         
     | 
| 
       13 
13 
     | 
    
         
             
                  class PropertyValueUpdater
         
     | 
| 
      
 14 
     | 
    
         
            +
                    extend T::Sig
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
         
     | 
| 
       14 
17 
     | 
    
         
             
                    def initialize(dependency_files:)
         
     | 
| 
       15 
18 
     | 
    
         
             
                      @dependency_files = dependency_files
         
     | 
| 
       16 
19 
     | 
    
         
             
                    end
         
     | 
| 
       17 
20 
     | 
    
         | 
| 
      
 21 
     | 
    
         
            +
                    sig do
         
     | 
| 
      
 22 
     | 
    
         
            +
                      params(property_name: String,
         
     | 
| 
      
 23 
     | 
    
         
            +
                             updated_value: String,
         
     | 
| 
      
 24 
     | 
    
         
            +
                             callsite_file: Dependabot::DependencyFile)
         
     | 
| 
      
 25 
     | 
    
         
            +
                        .returns(T::Array[Dependabot::DependencyFile])
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
       18 
27 
     | 
    
         
             
                    def update_files_for_property_change(property_name:, updated_value:,
         
     | 
| 
       19 
28 
     | 
    
         
             
                                                         callsite_file:)
         
     | 
| 
       20 
29 
     | 
    
         
             
                      declaration_details =
         
     | 
| 
       21 
     | 
    
         
            -
                        property_value_finder
         
     | 
| 
       22 
     | 
    
         
            -
                        .property_details(
         
     | 
| 
      
 30 
     | 
    
         
            +
                        property_value_finder.property_details(
         
     | 
| 
       23 
31 
     | 
    
         
             
                          property_name: property_name,
         
     | 
| 
       24 
32 
     | 
    
         
             
                          callsite_file: callsite_file
         
     | 
| 
       25 
33 
     | 
    
         
             
                        )
         
     | 
| 
      
 34 
     | 
    
         
            +
                      throw "Unable to locate property details" unless declaration_details
         
     | 
| 
       26 
35 
     | 
    
         | 
| 
      
 36 
     | 
    
         
            +
                      declaration_filename = declaration_details.fetch(:file)
         
     | 
| 
       27 
37 
     | 
    
         
             
                      declaration_file = dependency_files.find do |f|
         
     | 
| 
       28 
     | 
    
         
            -
                         
     | 
| 
      
 38 
     | 
    
         
            +
                        declaration_filename == f.name
         
     | 
| 
       29 
39 
     | 
    
         
             
                      end
         
     | 
| 
      
 40 
     | 
    
         
            +
                      throw "Unable to locate declaration file" unless declaration_file
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                      content = T.must(declaration_file.content)
         
     | 
| 
       30 
43 
     | 
    
         
             
                      node = declaration_details.fetch(:node)
         
     | 
| 
       31 
44 
     | 
    
         | 
| 
       32 
     | 
    
         
            -
                      updated_content =  
     | 
| 
      
 45 
     | 
    
         
            +
                      updated_content = content.sub(
         
     | 
| 
       33 
46 
     | 
    
         
             
                        %r{(<#{Regexp.quote(node.name)}(?:\s[^>]*)?>)
         
     | 
| 
       34 
47 
     | 
    
         
             
                           \s*#{Regexp.quote(node.content)}\s*
         
     | 
| 
       35 
48 
     | 
    
         
             
                           </#{Regexp.quote(node.name)}>}xm,
         
     | 
| 
         @@ -37,21 +50,25 @@ module Dependabot 
     | 
|
| 
       37 
50 
     | 
    
         
             
                      )
         
     | 
| 
       38 
51 
     | 
    
         | 
| 
       39 
52 
     | 
    
         
             
                      files = dependency_files.dup
         
     | 
| 
       40 
     | 
    
         
            -
                      files 
     | 
| 
      
 53 
     | 
    
         
            +
                      file_index = T.must(files.index(declaration_file))
         
     | 
| 
      
 54 
     | 
    
         
            +
                      files[file_index] =
         
     | 
| 
       41 
55 
     | 
    
         
             
                        update_file(file: declaration_file, content: updated_content)
         
     | 
| 
       42 
56 
     | 
    
         
             
                      files
         
     | 
| 
       43 
57 
     | 
    
         
             
                    end
         
     | 
| 
       44 
58 
     | 
    
         | 
| 
       45 
59 
     | 
    
         
             
                    private
         
     | 
| 
       46 
60 
     | 
    
         | 
| 
      
 61 
     | 
    
         
            +
                    sig { returns(T::Array[DependencyFile]) }
         
     | 
| 
       47 
62 
     | 
    
         
             
                    attr_reader :dependency_files
         
     | 
| 
       48 
63 
     | 
    
         | 
| 
      
 64 
     | 
    
         
            +
                    sig { returns(FileParser::PropertyValueFinder) }
         
     | 
| 
       49 
65 
     | 
    
         
             
                    def property_value_finder
         
     | 
| 
       50 
66 
     | 
    
         
             
                      @property_value_finder ||=
         
     | 
| 
       51 
     | 
    
         
            -
                         
     | 
| 
       52 
     | 
    
         
            -
                        .new(dependency_files: dependency_files)
         
     | 
| 
      
 67 
     | 
    
         
            +
                        T.let(FileParser::PropertyValueFinder
         
     | 
| 
      
 68 
     | 
    
         
            +
                        .new(dependency_files: dependency_files), T.nilable(FileParser::PropertyValueFinder))
         
     | 
| 
       53 
69 
     | 
    
         
             
                    end
         
     | 
| 
       54 
70 
     | 
    
         | 
| 
      
 71 
     | 
    
         
            +
                    sig { params(file: DependencyFile, content: String).returns(DependencyFile) }
         
     | 
| 
       55 
72 
     | 
    
         
             
                    def update_file(file:, content:)
         
     | 
| 
       56 
73 
     | 
    
         
             
                      updated_file = file.dup
         
     | 
| 
       57 
74 
     | 
    
         
             
                      updated_file.content = content
         
     | 
| 
         @@ -1,10 +1,11 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # typed:  
     | 
| 
      
 1 
     | 
    
         
            +
            # typed: strong
         
     | 
| 
       2 
2 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            require "dependabot/dependency_file"
         
     | 
| 
       5 
5 
     | 
    
         
             
            require "dependabot/file_updaters"
         
     | 
| 
       6 
6 
     | 
    
         
             
            require "dependabot/file_updaters/base"
         
     | 
| 
       7 
7 
     | 
    
         
             
            require "dependabot/nuget/native_helpers"
         
     | 
| 
      
 8 
     | 
    
         
            +
            require "dependabot/shared_helpers"
         
     | 
| 
       8 
9 
     | 
    
         
             
            require "sorbet-runtime"
         
     | 
| 
       9 
10 
     | 
    
         | 
| 
       10 
11 
     | 
    
         
             
            module Dependabot
         
     | 
| 
         @@ -17,6 +18,7 @@ module Dependabot 
     | 
|
| 
       17 
18 
     | 
    
         
             
                  require_relative "file_parser/dotnet_tools_json_parser"
         
     | 
| 
       18 
19 
     | 
    
         
             
                  require_relative "file_parser/packages_config_parser"
         
     | 
| 
       19 
20 
     | 
    
         | 
| 
      
 21 
     | 
    
         
            +
                  sig { override.returns(T::Array[Regexp]) }
         
     | 
| 
       20 
22 
     | 
    
         
             
                  def self.updated_files_regex
         
     | 
| 
       21 
23 
     | 
    
         
             
                    [
         
     | 
| 
       22 
24 
     | 
    
         
             
                      %r{^[^/]*\.([a-z]{2})?proj$},
         
     | 
| 
         @@ -29,32 +31,33 @@ module Dependabot 
     | 
|
| 
       29 
31 
     | 
    
         
             
                    ]
         
     | 
| 
       30 
32 
     | 
    
         
             
                  end
         
     | 
| 
       31 
33 
     | 
    
         | 
| 
      
 34 
     | 
    
         
            +
                  sig { override.returns(T::Array[Dependabot::DependencyFile]) }
         
     | 
| 
       32 
35 
     | 
    
         
             
                  def updated_dependency_files
         
     | 
| 
       33 
     | 
    
         
            -
                     
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
      
 36 
     | 
    
         
            +
                    base_dir = T.must(dependency_files.first).directory
         
     | 
| 
      
 37 
     | 
    
         
            +
                    SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
         
     | 
| 
      
 38 
     | 
    
         
            +
                      dependencies.each do |dependency|
         
     | 
| 
      
 39 
     | 
    
         
            +
                        try_update_projects(dependency) || try_update_json(dependency)
         
     | 
| 
      
 40 
     | 
    
         
            +
                      end
         
     | 
| 
      
 41 
     | 
    
         
            +
                      updated_files = dependency_files.filter_map do |f|
         
     | 
| 
      
 42 
     | 
    
         
            +
                        updated_content = File.read(dependency_file_path(f))
         
     | 
| 
      
 43 
     | 
    
         
            +
                        next if updated_content == f.content
         
     | 
| 
       36 
44 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
                      updated_content = File.read(dependency_file_path(f))
         
     | 
| 
       40 
     | 
    
         
            -
                      next if updated_content == f.content
         
     | 
| 
      
 45 
     | 
    
         
            +
                        normalized_content = normalize_content(f, updated_content)
         
     | 
| 
      
 46 
     | 
    
         
            +
                        next if normalized_content == f.content
         
     | 
| 
       41 
47 
     | 
    
         | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
                      next if normalized_content == f.content
         
     | 
| 
      
 48 
     | 
    
         
            +
                        next if only_deleted_lines?(f.content, normalized_content)
         
     | 
| 
       44 
49 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
      
 50 
     | 
    
         
            +
                        puts "The contents of file [#{f.name}] were updated."
         
     | 
| 
       46 
51 
     | 
    
         | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
      
 52 
     | 
    
         
            +
                        updated_file(file: f, content: normalized_content)
         
     | 
| 
      
 53 
     | 
    
         
            +
                      end
         
     | 
| 
      
 54 
     | 
    
         
            +
                      updated_files
         
     | 
| 
       48 
55 
     | 
    
         
             
                    end
         
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
                    # reset repo files
         
     | 
| 
       51 
     | 
    
         
            -
                    SharedHelpers.reset_git_repo(T.cast(repo_contents_path, String)) if repo_contents_path
         
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
                    updated_files
         
     | 
| 
       54 
56 
     | 
    
         
             
                  end
         
     | 
| 
       55 
57 
     | 
    
         | 
| 
       56 
58 
     | 
    
         
             
                  private
         
     | 
| 
       57 
59 
     | 
    
         | 
| 
      
 60 
     | 
    
         
            +
                  sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
         
     | 
| 
       58 
61 
     | 
    
         
             
                  def try_update_projects(dependency)
         
     | 
| 
       59 
62 
     | 
    
         
             
                    update_ran = T.let(false, T::Boolean)
         
     | 
| 
       60 
63 
     | 
    
         
             
                    checked_files = Set.new
         
     | 
| 
         @@ -64,7 +67,7 @@ module Dependabot 
     | 
|
| 
       64 
67 
     | 
    
         
             
                      project_dependencies = project_dependencies(project_file)
         
     | 
| 
       65 
68 
     | 
    
         
             
                      proj_path = dependency_file_path(project_file)
         
     | 
| 
       66 
69 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
                      next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name) 
     | 
| 
      
 70 
     | 
    
         
            +
                      next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name)&.zero? }
         
     | 
| 
       68 
71 
     | 
    
         | 
| 
       69 
72 
     | 
    
         
             
                      next unless repo_contents_path
         
     | 
| 
       70 
73 
     | 
    
         | 
| 
         @@ -79,16 +82,16 @@ module Dependabot 
     | 
|
| 
       79 
82 
     | 
    
         
             
                      end
         
     | 
| 
       80 
83 
     | 
    
         
             
                      update_ran = true
         
     | 
| 
       81 
84 
     | 
    
         
             
                    end
         
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
85 
     | 
    
         
             
                    update_ran
         
     | 
| 
       84 
86 
     | 
    
         
             
                  end
         
     | 
| 
       85 
87 
     | 
    
         | 
| 
      
 88 
     | 
    
         
            +
                  sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
         
     | 
| 
       86 
89 
     | 
    
         
             
                  def try_update_json(dependency)
         
     | 
| 
       87 
     | 
    
         
            -
                    if dotnet_tools_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name) 
     | 
| 
       88 
     | 
    
         
            -
                       global_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name) 
     | 
| 
      
 90 
     | 
    
         
            +
                    if dotnet_tools_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name)&.zero? } ||
         
     | 
| 
      
 91 
     | 
    
         
            +
                       global_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name)&.zero? }
         
     | 
| 
       89 
92 
     | 
    
         | 
| 
       90 
93 
     | 
    
         
             
                      # We just need to feed the updater a project file, grab the first
         
     | 
| 
       91 
     | 
    
         
            -
                      project_file = project_files.first
         
     | 
| 
      
 94 
     | 
    
         
            +
                      project_file = T.must(project_files.first)
         
     | 
| 
       92 
95 
     | 
    
         
             
                      proj_path = dependency_file_path(project_file)
         
     | 
| 
       93 
96 
     | 
    
         | 
| 
       94 
97 
     | 
    
         
             
                      return false unless repo_contents_path
         
     | 
| 
         @@ -109,13 +112,14 @@ module Dependabot 
     | 
|
| 
       109 
112 
     | 
    
         
             
                    # Tests need to track how many times we call the tooling updater to ensure we don't recurse needlessly
         
     | 
| 
       110 
113 
     | 
    
         
             
                    # Ideally we should find a way to not run this code in prod
         
     | 
| 
       111 
114 
     | 
    
         
             
                    # (or a better way to track calls made to NativeHelpers)
         
     | 
| 
       112 
     | 
    
         
            -
                    @update_tooling_calls ||= {}
         
     | 
| 
      
 115 
     | 
    
         
            +
                    @update_tooling_calls ||= T.let({}, T.nilable(T::Hash[String, Integer]))
         
     | 
| 
       113 
116 
     | 
    
         
             
                    key = proj_path + dependency.name
         
     | 
| 
       114 
     | 
    
         
            -
                     
     | 
| 
       115 
     | 
    
         
            -
                      @update_tooling_calls[key] 
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
       117 
     | 
    
         
            -
                       
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
      
 117 
     | 
    
         
            +
                    @update_tooling_calls[key] =
         
     | 
| 
      
 118 
     | 
    
         
            +
                      if @update_tooling_calls[key]
         
     | 
| 
      
 119 
     | 
    
         
            +
                        T.must(@update_tooling_calls[key]) + 1
         
     | 
| 
      
 120 
     | 
    
         
            +
                      else
         
     | 
| 
      
 121 
     | 
    
         
            +
                        1
         
     | 
| 
      
 122 
     | 
    
         
            +
                      end
         
     | 
| 
       119 
123 
     | 
    
         
             
                  end
         
     | 
| 
       120 
124 
     | 
    
         | 
| 
       121 
125 
     | 
    
         
             
                  # Don't call this from outside tests, we're only checking that we aren't recursing needlessly
         
     | 
| 
         @@ -124,6 +128,7 @@ module Dependabot 
     | 
|
| 
       124 
128 
     | 
    
         
             
                    @update_tooling_calls
         
     | 
| 
       125 
129 
     | 
    
         
             
                  end
         
     | 
| 
       126 
130 
     | 
    
         | 
| 
      
 131 
     | 
    
         
            +
                  sig { params(project_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::Dependency]) }
         
     | 
| 
       127 
132 
     | 
    
         
             
                  def project_dependencies(project_file)
         
     | 
| 
       128 
133 
     | 
    
         
             
                    # Collect all dependencies from the project file and associated packages.config
         
     | 
| 
       129 
134 
     | 
    
         
             
                    dependencies = project_file_parser.dependency_set(project_file: project_file).dependencies
         
     | 
| 
         @@ -134,38 +139,54 @@ module Dependabot 
     | 
|
| 
       134 
139 
     | 
    
         
             
                                                                   .dependency_set.dependencies
         
     | 
| 
       135 
140 
     | 
    
         
             
                  end
         
     | 
| 
       136 
141 
     | 
    
         | 
| 
      
 142 
     | 
    
         
            +
                  sig { params(project_file: Dependabot::DependencyFile).returns(T.nilable(Dependabot::DependencyFile)) }
         
     | 
| 
       137 
143 
     | 
    
         
             
                  def find_packages_config(project_file)
         
     | 
| 
       138 
144 
     | 
    
         
             
                    project_file_name = File.basename(project_file.name)
         
     | 
| 
       139 
145 
     | 
    
         
             
                    packages_config_path = project_file.name.gsub(project_file_name, "packages.config")
         
     | 
| 
       140 
146 
     | 
    
         
             
                    packages_config_files.find { |f| f.name == packages_config_path }
         
     | 
| 
       141 
147 
     | 
    
         
             
                  end
         
     | 
| 
       142 
148 
     | 
    
         | 
| 
      
 149 
     | 
    
         
            +
                  sig { returns(Dependabot::Nuget::FileParser::ProjectFileParser) }
         
     | 
| 
       143 
150 
     | 
    
         
             
                  def project_file_parser
         
     | 
| 
       144 
151 
     | 
    
         
             
                    @project_file_parser ||=
         
     | 
| 
       145 
     | 
    
         
            -
                       
     | 
| 
       146 
     | 
    
         
            -
                         
     | 
| 
       147 
     | 
    
         
            -
             
     | 
| 
       148 
     | 
    
         
            -
             
     | 
| 
      
 152 
     | 
    
         
            +
                      T.let(
         
     | 
| 
      
 153 
     | 
    
         
            +
                        FileParser::ProjectFileParser.new(
         
     | 
| 
      
 154 
     | 
    
         
            +
                          dependency_files: dependency_files,
         
     | 
| 
      
 155 
     | 
    
         
            +
                          credentials: credentials,
         
     | 
| 
      
 156 
     | 
    
         
            +
                          repo_contents_path: repo_contents_path
         
     | 
| 
      
 157 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 158 
     | 
    
         
            +
                        T.nilable(Dependabot::Nuget::FileParser::ProjectFileParser)
         
     | 
| 
       149 
159 
     | 
    
         
             
                      )
         
     | 
| 
       150 
160 
     | 
    
         
             
                  end
         
     | 
| 
       151 
161 
     | 
    
         | 
| 
      
 162 
     | 
    
         
            +
                  sig { returns(T::Array[Dependabot::Dependency]) }
         
     | 
| 
       152 
163 
     | 
    
         
             
                  def global_json_dependencies
         
     | 
| 
       153 
164 
     | 
    
         
             
                    return [] unless global_json
         
     | 
| 
       154 
165 
     | 
    
         | 
| 
       155 
166 
     | 
    
         
             
                    @global_json_dependencies ||=
         
     | 
| 
       156 
     | 
    
         
            -
                       
     | 
| 
      
 167 
     | 
    
         
            +
                      T.let(
         
     | 
| 
      
 168 
     | 
    
         
            +
                        FileParser::GlobalJsonParser.new(global_json: T.must(global_json)).dependency_set.dependencies,
         
     | 
| 
      
 169 
     | 
    
         
            +
                        T.nilable(T::Array[Dependabot::Dependency])
         
     | 
| 
      
 170 
     | 
    
         
            +
                      )
         
     | 
| 
       157 
171 
     | 
    
         
             
                  end
         
     | 
| 
       158 
172 
     | 
    
         | 
| 
      
 173 
     | 
    
         
            +
                  sig { returns(T::Array[Dependabot::Dependency]) }
         
     | 
| 
       159 
174 
     | 
    
         
             
                  def dotnet_tools_json_dependencies
         
     | 
| 
       160 
175 
     | 
    
         
             
                    return [] unless dotnet_tools_json
         
     | 
| 
       161 
176 
     | 
    
         | 
| 
       162 
177 
     | 
    
         
             
                    @dotnet_tools_json_dependencies ||=
         
     | 
| 
       163 
     | 
    
         
            -
                       
     | 
| 
      
 178 
     | 
    
         
            +
                      T.let(
         
     | 
| 
      
 179 
     | 
    
         
            +
                        FileParser::DotNetToolsJsonParser.new(dotnet_tools_json: T.must(dotnet_tools_json))
         
     | 
| 
      
 180 
     | 
    
         
            +
                                                         .dependency_set.dependencies,
         
     | 
| 
      
 181 
     | 
    
         
            +
                        T.nilable(T::Array[Dependabot::Dependency])
         
     | 
| 
      
 182 
     | 
    
         
            +
                      )
         
     | 
| 
       164 
183 
     | 
    
         
             
                  end
         
     | 
| 
       165 
184 
     | 
    
         | 
| 
      
 185 
     | 
    
         
            +
                  # rubocop:disable Metrics/PerceivedComplexity
         
     | 
| 
      
 186 
     | 
    
         
            +
                  sig { params(dependency_file: Dependabot::DependencyFile, updated_content: String).returns(String) }
         
     | 
| 
       166 
187 
     | 
    
         
             
                  def normalize_content(dependency_file, updated_content)
         
     | 
| 
       167 
188 
     | 
    
         
             
                    # Fix up line endings
         
     | 
| 
       168 
     | 
    
         
            -
                    if dependency_file.content 
     | 
| 
      
 189 
     | 
    
         
            +
                    if dependency_file.content&.include?("\r\n") && updated_content.match?(/(?<!\r)\n/)
         
     | 
| 
       169 
190 
     | 
    
         
             
                      # The original content contain windows style newlines.
         
     | 
| 
       170 
191 
     | 
    
         
             
                      # Ensure the updated content also uses windows style newlines.
         
     | 
| 
       171 
192 
     | 
    
         
             
                      updated_content = updated_content.gsub(/(?<!\r)\n/, "\r\n")
         
     | 
| 
         @@ -178,19 +199,21 @@ module Dependabot 
     | 
|
| 
       178 
199 
     | 
    
         
             
                    end
         
     | 
| 
       179 
200 
     | 
    
         | 
| 
       180 
201 
     | 
    
         
             
                    # Fix up BOM
         
     | 
| 
       181 
     | 
    
         
            -
                    if !dependency_file.content 
     | 
| 
      
 202 
     | 
    
         
            +
                    if !dependency_file.content&.start_with?("\uFEFF") && updated_content.start_with?("\uFEFF")
         
     | 
| 
       182 
203 
     | 
    
         
             
                      updated_content = updated_content.delete_prefix("\uFEFF")
         
     | 
| 
       183 
204 
     | 
    
         
             
                      puts "Removing BOM from [#{dependency_file.name}]."
         
     | 
| 
       184 
     | 
    
         
            -
                    elsif dependency_file.content 
     | 
| 
      
 205 
     | 
    
         
            +
                    elsif dependency_file.content&.start_with?("\uFEFF") && !updated_content.start_with?("\uFEFF")
         
     | 
| 
       185 
206 
     | 
    
         
             
                      updated_content = "\uFEFF" + updated_content
         
     | 
| 
       186 
207 
     | 
    
         
             
                      puts "Adding BOM to [#{dependency_file.name}]."
         
     | 
| 
       187 
208 
     | 
    
         
             
                    end
         
     | 
| 
       188 
209 
     | 
    
         | 
| 
       189 
210 
     | 
    
         
             
                    updated_content
         
     | 
| 
       190 
211 
     | 
    
         
             
                  end
         
     | 
| 
      
 212 
     | 
    
         
            +
                  # rubocop:enable Metrics/PerceivedComplexity
         
     | 
| 
       191 
213 
     | 
    
         | 
| 
      
 214 
     | 
    
         
            +
                  sig { params(dependency_file: Dependabot::DependencyFile).returns(String) }
         
     | 
| 
       192 
215 
     | 
    
         
             
                  def dependency_file_path(dependency_file)
         
     | 
| 
       193 
     | 
    
         
            -
                    if dependency_file.directory.start_with?(repo_contents_path)
         
     | 
| 
      
 216 
     | 
    
         
            +
                    if dependency_file.directory.start_with?(T.must(repo_contents_path))
         
     | 
| 
       194 
217 
     | 
    
         
             
                      File.join(dependency_file.directory, dependency_file.name)
         
     | 
| 
       195 
218 
     | 
    
         
             
                    else
         
     | 
| 
       196 
219 
     | 
    
         
             
                      file_directory = dependency_file.directory
         
     | 
| 
         @@ -199,29 +222,42 @@ module Dependabot 
     | 
|
| 
       199 
222 
     | 
    
         
             
                    end
         
     | 
| 
       200 
223 
     | 
    
         
             
                  end
         
     | 
| 
       201 
224 
     | 
    
         | 
| 
      
 225 
     | 
    
         
            +
                  sig { returns(T::Array[Dependabot::DependencyFile]) }
         
     | 
| 
       202 
226 
     | 
    
         
             
                  def project_files
         
     | 
| 
       203 
227 
     | 
    
         
             
                    dependency_files.select { |df| df.name.match?(/\.([a-z]{2})?proj$/) }
         
     | 
| 
       204 
228 
     | 
    
         
             
                  end
         
     | 
| 
       205 
229 
     | 
    
         | 
| 
      
 230 
     | 
    
         
            +
                  sig { returns(T::Array[Dependabot::DependencyFile]) }
         
     | 
| 
       206 
231 
     | 
    
         
             
                  def packages_config_files
         
     | 
| 
       207 
232 
     | 
    
         
             
                    dependency_files.select do |f|
         
     | 
| 
       208 
233 
     | 
    
         
             
                      T.must(T.must(f.name.split("/").last).casecmp("packages.config")).zero?
         
     | 
| 
       209 
234 
     | 
    
         
             
                    end
         
     | 
| 
       210 
235 
     | 
    
         
             
                  end
         
     | 
| 
       211 
236 
     | 
    
         | 
| 
      
 237 
     | 
    
         
            +
                  sig { returns(T.nilable(Dependabot::DependencyFile)) }
         
     | 
| 
       212 
238 
     | 
    
         
             
                  def global_json
         
     | 
| 
       213 
239 
     | 
    
         
             
                    dependency_files.find { |f| T.must(f.name.casecmp("global.json")).zero? }
         
     | 
| 
       214 
240 
     | 
    
         
             
                  end
         
     | 
| 
       215 
241 
     | 
    
         | 
| 
      
 242 
     | 
    
         
            +
                  sig { returns(T.nilable(Dependabot::DependencyFile)) }
         
     | 
| 
       216 
243 
     | 
    
         
             
                  def dotnet_tools_json
         
     | 
| 
       217 
244 
     | 
    
         
             
                    dependency_files.find { |f| T.must(f.name.casecmp(".config/dotnet-tools.json")).zero? }
         
     | 
| 
       218 
245 
     | 
    
         
             
                  end
         
     | 
| 
       219 
246 
     | 
    
         | 
| 
      
 247 
     | 
    
         
            +
                  sig { override.void }
         
     | 
| 
       220 
248 
     | 
    
         
             
                  def check_required_files
         
     | 
| 
       221 
249 
     | 
    
         
             
                    return if project_files.any? || packages_config_files.any?
         
     | 
| 
       222 
250 
     | 
    
         | 
| 
       223 
251 
     | 
    
         
             
                    raise "No project file or packages.config!"
         
     | 
| 
       224 
252 
     | 
    
         
             
                  end
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
                  sig { params(original_content: T.nilable(String), updated_content: String).returns(T::Boolean) }
         
     | 
| 
      
 255 
     | 
    
         
            +
                  def only_deleted_lines?(original_content, updated_content)
         
     | 
| 
      
 256 
     | 
    
         
            +
                    original_lines = original_content&.lines || []
         
     | 
| 
      
 257 
     | 
    
         
            +
                    updated_lines = updated_content.lines
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
                    original_lines.count > updated_lines.count
         
     | 
| 
      
 260 
     | 
    
         
            +
                  end
         
     | 
| 
       225 
261 
     | 
    
         
             
                end
         
     | 
| 
       226 
262 
     | 
    
         
             
              end
         
     | 
| 
       227 
263 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require "sorbet-runtime"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Dependabot
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Nuget
         
     | 
| 
      
 8 
     | 
    
         
            +
                module HttpResponseHelpers
         
     | 
| 
      
 9 
     | 
    
         
            +
                  extend T::Sig
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  sig { params(string: String).returns(String) }
         
     | 
| 
      
 12 
     | 
    
         
            +
                  def self.remove_wrapping_zero_width_chars(string)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    string.force_encoding("UTF-8").encode
         
     | 
| 
      
 14 
     | 
    
         
            +
                          .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
         
     | 
| 
      
 15 
     | 
    
         
            +
                          .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # typed:  
     | 
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
       2 
2 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            require "nokogiri"
         
     | 
| 
         @@ -12,13 +12,30 @@ module Dependabot 
     | 
|
| 
       12 
12 
     | 
    
         
             
                class MetadataFinder < Dependabot::MetadataFinders::Base
         
     | 
| 
       13 
13 
     | 
    
         
             
                  extend T::Sig
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
      
 15 
     | 
    
         
            +
                  sig do
         
     | 
| 
      
 16 
     | 
    
         
            +
                    override
         
     | 
| 
      
 17 
     | 
    
         
            +
                      .params(
         
     | 
| 
      
 18 
     | 
    
         
            +
                        dependency: Dependabot::Dependency,
         
     | 
| 
      
 19 
     | 
    
         
            +
                        credentials: T::Array[Dependabot::Credential]
         
     | 
| 
      
 20 
     | 
    
         
            +
                      )
         
     | 
| 
      
 21 
     | 
    
         
            +
                      .void
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  def initialize(dependency:, credentials:)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @dependency_nuspec_file = T.let(nil, T.nilable(Nokogiri::XML::Document))
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                    super
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
       15 
29 
     | 
    
         
             
                  private
         
     | 
| 
       16 
30 
     | 
    
         | 
| 
      
 31 
     | 
    
         
            +
                  sig { override.returns(T.nilable(Dependabot::Source)) }
         
     | 
| 
       17 
32 
     | 
    
         
             
                  def look_up_source
         
     | 
| 
       18 
33 
     | 
    
         
             
                    return Source.from_url(dependency_source_url) if dependency_source_url
         
     | 
| 
       19 
34 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
                     
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
      
 35 
     | 
    
         
            +
                    if dependency_nuspec_file
         
     | 
| 
      
 36 
     | 
    
         
            +
                      src_repo = look_up_source_in_nuspec(T.must(dependency_nuspec_file))
         
     | 
| 
      
 37 
     | 
    
         
            +
                      return src_repo if src_repo
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
       22 
39 
     | 
    
         | 
| 
       23 
40 
     | 
    
         
             
                    # Fallback to getting source from the search result's projectUrl or licenseUrl.
         
     | 
| 
       24 
41 
     | 
    
         
             
                    # GitHub Packages doesn't support getting the `.nuspec`, switch to getting
         
     | 
| 
         @@ -31,6 +48,7 @@ module Dependabot 
     | 
|
| 
       31 
48 
     | 
    
         
             
                    nil
         
     | 
| 
       32 
49 
     | 
    
         
             
                  end
         
     | 
| 
       33 
50 
     | 
    
         | 
| 
      
 51 
     | 
    
         
            +
                  sig { returns(T.nilable(Dependabot::Source)) }
         
     | 
| 
       34 
52 
     | 
    
         
             
                  def src_repo_from_project
         
     | 
| 
       35 
53 
     | 
    
         
             
                    source = dependency.requirements.find { |r| r.fetch(:source) }&.fetch(:source)
         
     | 
| 
       36 
54 
     | 
    
         
             
                    return unless source
         
     | 
| 
         @@ -58,6 +76,7 @@ module Dependabot 
     | 
|
| 
       58 
76 
     | 
    
         
             
                    # Ignored, this is expected for some registries that don't handle these request.
         
     | 
| 
       59 
77 
     | 
    
         
             
                  end
         
     | 
| 
       60 
78 
     | 
    
         | 
| 
      
 79 
     | 
    
         
            +
                  sig { params(body: String).returns(T.nilable(String)) }
         
     | 
| 
       61 
80 
     | 
    
         
             
                  def extract_search_url(body)
         
     | 
| 
       62 
81 
     | 
    
         
             
                    JSON.parse(body)
         
     | 
| 
       63 
82 
     | 
    
         
             
                        .fetch("resources", [])
         
     | 
| 
         @@ -65,6 +84,7 @@ module Dependabot 
     | 
|
| 
       65 
84 
     | 
    
         
             
                        &.fetch("@id")
         
     | 
| 
       66 
85 
     | 
    
         
             
                  end
         
     | 
| 
       67 
86 
     | 
    
         | 
| 
      
 87 
     | 
    
         
            +
                  sig { params(body: String).returns(T.nilable(Dependabot::Source)) }
         
     | 
| 
       68 
88 
     | 
    
         
             
                  def extract_source_repo(body)
         
     | 
| 
       69 
89 
     | 
    
         
             
                    JSON.parse(body).fetch("data", []).each do |search_result|
         
     | 
| 
       70 
90 
     | 
    
         
             
                      next unless search_result["id"].casecmp(dependency.name).zero?
         
     | 
| 
         @@ -82,6 +102,7 @@ module Dependabot 
     | 
|
| 
       82 
102 
     | 
    
         
             
                    nil
         
     | 
| 
       83 
103 
     | 
    
         
             
                  end
         
     | 
| 
       84 
104 
     | 
    
         | 
| 
      
 105 
     | 
    
         
            +
                  sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(Dependabot::Source)) }
         
     | 
| 
       85 
106 
     | 
    
         
             
                  def look_up_source_in_nuspec(nuspec)
         
     | 
| 
       86 
107 
     | 
    
         
             
                    potential_source_urls = [
         
     | 
| 
       87 
108 
     | 
    
         
             
                      nuspec.at_css("package > metadata > repository")
         
     | 
| 
         @@ -97,6 +118,7 @@ module Dependabot 
     | 
|
| 
       97 
118 
     | 
    
         
             
                    Source.from_url(source_url)
         
     | 
| 
       98 
119 
     | 
    
         
             
                  end
         
     | 
| 
       99 
120 
     | 
    
         | 
| 
      
 121 
     | 
    
         
            +
                  sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(String)) }
         
     | 
| 
       100 
122 
     | 
    
         
             
                  def source_from_anywhere_in_nuspec(nuspec)
         
     | 
| 
       101 
123 
     | 
    
         
             
                    github_urls = []
         
     | 
| 
       102 
124 
     | 
    
         
             
                    nuspec.to_s.force_encoding(Encoding::UTF_8)
         
     | 
| 
         @@ -110,17 +132,21 @@ module Dependabot 
     | 
|
| 
       110 
132 
     | 
    
         
             
                    end
         
     | 
| 
       111 
133 
     | 
    
         
             
                  end
         
     | 
| 
       112 
134 
     | 
    
         | 
| 
      
 135 
     | 
    
         
            +
                  sig { returns(T.nilable(Nokogiri::XML::Document)) }
         
     | 
| 
       113 
136 
     | 
    
         
             
                  def dependency_nuspec_file
         
     | 
| 
       114 
137 
     | 
    
         
             
                    return @dependency_nuspec_file unless @dependency_nuspec_file.nil?
         
     | 
| 
       115 
138 
     | 
    
         | 
| 
      
 139 
     | 
    
         
            +
                    return if dependency_nuspec_url.nil?
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
       116 
141 
     | 
    
         
             
                    response = Dependabot::RegistryClient.get(
         
     | 
| 
       117 
     | 
    
         
            -
                      url: dependency_nuspec_url,
         
     | 
| 
      
 142 
     | 
    
         
            +
                      url: T.must(dependency_nuspec_url),
         
     | 
| 
       118 
143 
     | 
    
         
             
                      headers: auth_header
         
     | 
| 
       119 
144 
     | 
    
         
             
                    )
         
     | 
| 
       120 
145 
     | 
    
         | 
| 
       121 
146 
     | 
    
         
             
                    @dependency_nuspec_file = Nokogiri::XML(response.body)
         
     | 
| 
       122 
147 
     | 
    
         
             
                  end
         
     | 
| 
       123 
148 
     | 
    
         | 
| 
      
 149 
     | 
    
         
            +
                  sig { returns(T.nilable(String)) }
         
     | 
| 
       124 
150 
     | 
    
         
             
                  def dependency_nuspec_url
         
     | 
| 
       125 
151 
     | 
    
         
             
                    source = dependency.requirements
         
     | 
| 
       126 
152 
     | 
    
         
             
                                       .find { |r| r.fetch(:source) }&.fetch(:source)
         
     | 
| 
         @@ -128,6 +154,7 @@ module Dependabot 
     | 
|
| 
       128 
154 
     | 
    
         
             
                    source.fetch(:nuspec_url) if source&.key?(:nuspec_url)
         
     | 
| 
       129 
155 
     | 
    
         
             
                  end
         
     | 
| 
       130 
156 
     | 
    
         | 
| 
      
 157 
     | 
    
         
            +
                  sig { returns(T.nilable(String)) }
         
     | 
| 
       131 
158 
     | 
    
         
             
                  def dependency_source_url
         
     | 
| 
       132 
159 
     | 
    
         
             
                    source = dependency.requirements
         
     | 
| 
       133 
160 
     | 
    
         
             
                                       .find { |r| r.fetch(:source) }&.fetch(:source)
         
     | 
| 
         @@ -139,6 +166,7 @@ module Dependabot 
     | 
|
| 
       139 
166 
     | 
    
         
             
                  end
         
     | 
| 
       140 
167 
     | 
    
         | 
| 
       141 
168 
     | 
    
         
             
                  # rubocop:disable Metrics/PerceivedComplexity
         
     | 
| 
      
 169 
     | 
    
         
            +
                  sig { returns(T::Hash[String, String]) }
         
     | 
| 
       142 
170 
     | 
    
         
             
                  def auth_header
         
     | 
| 
       143 
171 
     | 
    
         
             
                    source = dependency.requirements
         
     | 
| 
       144 
172 
     | 
    
         
             
                                       .find { |r| r.fetch(:source) }&.fetch(:source)
         
     | 
| 
         @@ -2,6 +2,7 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            require "dependabot/nuget/cache_manager"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "dependabot/nuget/http_response_helpers"
         
     | 
| 
       5 
6 
     | 
    
         
             
            require "dependabot/nuget/update_checker/repository_finder"
         
     | 
| 
       6 
7 
     | 
    
         
             
            require "sorbet-runtime"
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
         @@ -20,11 +21,33 @@ module Dependabot 
     | 
|
| 
       20 
21 
     | 
    
         
             
                      get_package_versions_v3(dependency_name, repository_details)
         
     | 
| 
       21 
22 
     | 
    
         
             
                    elsif repository_type == "v2"
         
     | 
| 
       22 
23 
     | 
    
         
             
                      get_package_versions_v2(dependency_name, repository_details)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    elsif repository_type == "local"
         
     | 
| 
      
 25 
     | 
    
         
            +
                      get_package_versions_local(dependency_name, repository_details)
         
     | 
| 
       23 
26 
     | 
    
         
             
                    else
         
     | 
| 
       24 
27 
     | 
    
         
             
                      raise "Unknown repository type: #{repository_type}"
         
     | 
| 
       25 
28 
     | 
    
         
             
                    end
         
     | 
| 
       26 
29 
     | 
    
         
             
                  end
         
     | 
| 
       27 
30 
     | 
    
         | 
| 
      
 31 
     | 
    
         
            +
                  sig do
         
     | 
| 
      
 32 
     | 
    
         
            +
                    params(dependency_name: String, repository_details: T::Hash[Symbol, String])
         
     | 
| 
      
 33 
     | 
    
         
            +
                      .returns(T.nilable(T::Set[String]))
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                  private_class_method def self.get_package_versions_local(dependency_name, repository_details)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    url = repository_details.fetch(:base_url)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    raise "Local repo #{url} doesn't exist or isn't a directory" unless File.exist?(url) && File.directory?(url)
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    package_dir = File.join(url, dependency_name)
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    versions = Set.new
         
     | 
| 
      
 42 
     | 
    
         
            +
                    return versions unless File.exist?(package_dir) && File.directory?(package_dir)
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    Dir.each_child(package_dir) do |child|
         
     | 
| 
      
 45 
     | 
    
         
            +
                      versions.add(child) if File.directory?(File.join(package_dir, child))
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    versions
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
       28 
51 
     | 
    
         
             
                  sig do
         
     | 
| 
       29 
52 
     | 
    
         
             
                    params(dependency_name: String, repository_details: T::Hash[Symbol, String])
         
     | 
| 
       30 
53 
     | 
    
         
             
                      .returns(T.nilable(T::Set[String]))
         
     | 
| 
         @@ -52,14 +75,15 @@ module Dependabot 
     | 
|
| 
       52 
75 
     | 
    
         
             
                    doc = execute_xml_nuget_request(repository_details.fetch(:versions_url), repository_details)
         
     | 
| 
       53 
76 
     | 
    
         
             
                    return unless doc
         
     | 
| 
       54 
77 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
                     
     | 
| 
      
 78 
     | 
    
         
            +
                    # v2 APIs can differ, but all tested have this title value set to the name of the package
         
     | 
| 
      
 79 
     | 
    
         
            +
                    title_nodes = doc.xpath("/feed/entry/title")
         
     | 
| 
       56 
80 
     | 
    
         
             
                    matching_versions = Set.new
         
     | 
| 
       57 
     | 
    
         
            -
                     
     | 
| 
       58 
     | 
    
         
            -
                      return nil unless  
     | 
| 
      
 81 
     | 
    
         
            +
                    title_nodes.each do |title_node|
         
     | 
| 
      
 82 
     | 
    
         
            +
                      return nil unless title_node.text
         
     | 
| 
       59 
83 
     | 
    
         | 
| 
       60 
     | 
    
         
            -
                      next unless  
     | 
| 
      
 84 
     | 
    
         
            +
                      next unless title_node.text.casecmp?(dependency_name)
         
     | 
| 
       61 
85 
     | 
    
         | 
| 
       62 
     | 
    
         
            -
                      version_node =  
     | 
| 
      
 86 
     | 
    
         
            +
                      version_node = title_node.parent.xpath("properties/Version")
         
     | 
| 
       63 
87 
     | 
    
         
             
                      matching_versions << version_node.text if version_node && version_node.text
         
     | 
| 
       64 
88 
     | 
    
         
             
                    end
         
     | 
| 
       65 
89 
     | 
    
         | 
| 
         @@ -131,6 +155,7 @@ module Dependabot 
     | 
|
| 
       131 
155 
     | 
    
         
             
                        &.find { |d| d.fetch("id").casecmp(dependency_name.downcase).zero? }
         
     | 
| 
       132 
156 
     | 
    
         
             
                        &.fetch("versions")
         
     | 
| 
       133 
157 
     | 
    
         
             
                        &.map { |d| d.fetch("version") }
         
     | 
| 
      
 158 
     | 
    
         
            +
                        &.to_set
         
     | 
| 
       134 
159 
     | 
    
         
             
                  end
         
     | 
| 
       135 
160 
     | 
    
         | 
| 
       136 
161 
     | 
    
         
             
                  sig do
         
     | 
| 
         @@ -162,7 +187,7 @@ module Dependabot 
     | 
|
| 
       162 
187 
     | 
    
         
             
                    )
         
     | 
| 
       163 
188 
     | 
    
         
             
                    return unless response.status == 200
         
     | 
| 
       164 
189 
     | 
    
         | 
| 
       165 
     | 
    
         
            -
                    body = remove_wrapping_zero_width_chars(response.body)
         
     | 
| 
      
 190 
     | 
    
         
            +
                    body = HttpResponseHelpers.remove_wrapping_zero_width_chars(response.body)
         
     | 
| 
       166 
191 
     | 
    
         
             
                    JSON.parse(body)
         
     | 
| 
       167 
192 
     | 
    
         
             
                  end
         
     | 
| 
       168 
193 
     | 
    
         | 
| 
         @@ -193,13 +218,6 @@ module Dependabot 
     | 
|
| 
       193 
218 
     | 
    
         | 
| 
       194 
219 
     | 
    
         
             
                    raise PrivateSourceTimedOut, repo_url
         
     | 
| 
       195 
220 
     | 
    
         
             
                  end
         
     | 
| 
       196 
     | 
    
         
            -
             
     | 
| 
       197 
     | 
    
         
            -
                  sig { params(string: String).returns(String) }
         
     | 
| 
       198 
     | 
    
         
            -
                  private_class_method def self.remove_wrapping_zero_width_chars(string)
         
     | 
| 
       199 
     | 
    
         
            -
                    string.force_encoding("UTF-8").encode
         
     | 
| 
       200 
     | 
    
         
            -
                          .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
         
     | 
| 
       201 
     | 
    
         
            -
                          .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
         
     | 
| 
       202 
     | 
    
         
            -
                  end
         
     | 
| 
       203 
221 
     | 
    
         
             
                end
         
     | 
| 
       204 
222 
     | 
    
         
             
              end
         
     | 
| 
       205 
223 
     | 
    
         
             
            end
         
     | 
| 
         @@ -60,6 +60,7 @@ module Dependabot 
     | 
|
| 
       60 
60 
     | 
    
         
             
                    convert_wildcard_req(req_string)
         
     | 
| 
       61 
61 
     | 
    
         
             
                  end
         
     | 
| 
       62 
62 
     | 
    
         | 
| 
      
 63 
     | 
    
         
            +
                  # rubocop:disable Metrics/PerceivedComplexity
         
     | 
| 
       63 
64 
     | 
    
         
             
                  def convert_dotnet_range_to_ruby_range(req_string)
         
     | 
| 
       64 
65 
     | 
    
         
             
                    lower_b, upper_b = req_string.split(",").map(&:strip).map do |bound|
         
     | 
| 
       65 
66 
     | 
    
         
             
                      next convert_range_wildcard_req(bound) if bound.include?("*")
         
     | 
| 
         @@ -75,7 +76,8 @@ module Dependabot 
     | 
|
| 
       75 
76 
     | 
    
         
             
                      end
         
     | 
| 
       76 
77 
     | 
    
         | 
| 
       77 
78 
     | 
    
         
             
                    upper_b =
         
     | 
| 
       78 
     | 
    
         
            -
                      if  
     | 
| 
      
 79 
     | 
    
         
            +
                      if !upper_b then nil
         
     | 
| 
      
 80 
     | 
    
         
            +
                      elsif [")", "]"].include?(upper_b) then nil
         
     | 
| 
       79 
81 
     | 
    
         
             
                      elsif upper_b.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}"
         
     | 
| 
       80 
82 
     | 
    
         
             
                      else
         
     | 
| 
       81 
83 
     | 
    
         
             
                        "<= #{upper_b.sub(/\s*\]/, '').strip}"
         
     | 
| 
         @@ -83,6 +85,7 @@ module Dependabot 
     | 
|
| 
       83 
85 
     | 
    
         | 
| 
       84 
86 
     | 
    
         
             
                    [lower_b, upper_b].compact
         
     | 
| 
       85 
87 
     | 
    
         
             
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                  # rubocop:enable Metrics/PerceivedComplexity
         
     | 
| 
       86 
89 
     | 
    
         | 
| 
       87 
90 
     | 
    
         
             
                  def convert_range_wildcard_req(req_string)
         
     | 
| 
       88 
91 
     | 
    
         
             
                    range_end = req_string[-1]
         
     |