|
| 1 | +# typed: strict |
| 2 | +# frozen_string_literal: true |
| 3 | + |
| 4 | +require "sorbet-runtime" |
| 5 | +require "dependabot/dependency" |
| 6 | +require "dependabot/update_checkers" |
| 7 | +require "dependabot/pub/version" |
| 8 | +require "dependabot/pre_commit/additional_dependency_checkers" |
| 9 | +require "dependabot/pre_commit/additional_dependency_checkers/base" |
| 10 | + |
| 11 | +module Dependabot |
| 12 | + module PreCommit |
| 13 | + module AdditionalDependencyCheckers |
| 14 | + class Dart < Base |
| 15 | + extend T::Sig |
| 16 | + |
| 17 | + sig { override.returns(T.nilable(String)) } |
| 18 | + def latest_version |
| 19 | + return nil unless package_name |
| 20 | + |
| 21 | + @latest_version ||= T.let( |
| 22 | + fetch_latest_version_via_pub_checker, |
| 23 | + T.nilable(String) |
| 24 | + ) |
| 25 | + end |
| 26 | + |
| 27 | + sig { override.params(latest_version: String).returns(T::Array[T::Hash[Symbol, T.untyped]]) } |
| 28 | + def updated_requirements(latest_version) |
| 29 | + requirements.map do |original_req| |
| 30 | + original_source = original_req[:source] |
| 31 | + next original_req unless original_source.is_a?(Hash) |
| 32 | + next original_req unless original_source[:type] == "additional_dependency" |
| 33 | + |
| 34 | + original_requirement = original_req[:requirement] |
| 35 | + new_requirement = build_updated_requirement(original_requirement, latest_version) |
| 36 | + |
| 37 | + new_original_string = build_original_string( |
| 38 | + package_name: original_source[:original_name] || original_source[:package_name], |
| 39 | + requirement: new_requirement |
| 40 | + ) |
| 41 | + |
| 42 | + new_source = original_source.merge(original_string: new_original_string) |
| 43 | + |
| 44 | + original_req.merge( |
| 45 | + requirement: new_requirement, |
| 46 | + source: new_source |
| 47 | + ) |
| 48 | + end |
| 49 | + end |
| 50 | + |
| 51 | + private |
| 52 | + |
| 53 | + sig { returns(T.nilable(String)) } |
| 54 | + def fetch_latest_version_via_pub_checker |
| 55 | + pub_checker = pub_update_checker |
| 56 | + return nil unless pub_checker |
| 57 | + |
| 58 | + latest = pub_checker.latest_version |
| 59 | + Dependabot.logger.info("Pub UpdateChecker found latest version: #{latest || 'none'}") |
| 60 | + |
| 61 | + latest&.to_s |
| 62 | + rescue Dependabot::DependabotError, Excon::Error => e |
| 63 | + Dependabot.logger.debug("Error checking Dart package #{package_name}: #{e.message}") |
| 64 | + nil |
| 65 | + end |
| 66 | + |
| 67 | + sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) } |
| 68 | + def pub_update_checker |
| 69 | + @pub_update_checker ||= T.let( |
| 70 | + build_pub_update_checker, |
| 71 | + T.nilable(Dependabot::UpdateCheckers::Base) |
| 72 | + ) |
| 73 | + end |
| 74 | + |
| 75 | + sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) } |
| 76 | + def build_pub_update_checker |
| 77 | + pub_dependency = build_pub_dependency |
| 78 | + return nil unless pub_dependency |
| 79 | + |
| 80 | + Dependabot.logger.info("Delegating to pub UpdateChecker for package: #{pub_dependency.name}") |
| 81 | + |
| 82 | + Dependabot::UpdateCheckers.for_package_manager("pub").new( |
| 83 | + dependency: pub_dependency, |
| 84 | + dependency_files: build_pub_dependency_files, |
| 85 | + credentials: credentials, |
| 86 | + ignored_versions: [], |
| 87 | + security_advisories: [], |
| 88 | + raise_on_ignored: false |
| 89 | + ) |
| 90 | + end |
| 91 | + |
| 92 | + sig { returns(T.nilable(Dependabot::Dependency)) } |
| 93 | + def build_pub_dependency |
| 94 | + return nil unless package_name |
| 95 | + |
| 96 | + version = current_version || extract_version_from_requirement |
| 97 | + |
| 98 | + Dependabot::Dependency.new( |
| 99 | + name: T.must(package_name), |
| 100 | + version: version, |
| 101 | + requirements: [{ |
| 102 | + requirement: version ? "^#{version}" : nil, |
| 103 | + groups: ["dependencies"], |
| 104 | + file: "pubspec.yaml", |
| 105 | + source: nil |
| 106 | + }], |
| 107 | + package_manager: "pub" |
| 108 | + ) |
| 109 | + end |
| 110 | + |
| 111 | + sig { returns(T.nilable(String)) } |
| 112 | + def extract_version_from_requirement |
| 113 | + req_string = requirements.first&.dig(:requirement) |
| 114 | + return nil unless req_string |
| 115 | + |
| 116 | + version_part = req_string.to_s.sub(/\A(?:[~^]|[><=]+)\s*/, "") |
| 117 | + return version_part if Dependabot::Pub::Version.correct?(version_part) |
| 118 | + |
| 119 | + nil |
| 120 | + end |
| 121 | + |
| 122 | + sig { returns(T::Array[Dependabot::DependencyFile]) } |
| 123 | + def build_pub_dependency_files |
| 124 | + version = current_version || extract_version_from_requirement |
| 125 | + requirement = version ? "^#{version}" : "any" |
| 126 | + |
| 127 | + pubspec_content = <<~YAML |
| 128 | + name: dependabot_pre_commit_check |
| 129 | + version: 0.0.1 |
| 130 | + environment: |
| 131 | + sdk: ">=3.0.0 <4.0.0" |
| 132 | + dependencies: |
| 133 | + #{T.must(package_name)}: #{requirement} |
| 134 | + YAML |
| 135 | + |
| 136 | + [ |
| 137 | + Dependabot::DependencyFile.new( |
| 138 | + name: "pubspec.yaml", |
| 139 | + content: pubspec_content |
| 140 | + ) |
| 141 | + ] |
| 142 | + end |
| 143 | + |
| 144 | + sig do |
| 145 | + params( |
| 146 | + package_name: T.nilable(String), |
| 147 | + requirement: T.nilable(String) |
| 148 | + ).returns(String) |
| 149 | + end |
| 150 | + def build_original_string(package_name:, requirement:) |
| 151 | + base = package_name.to_s |
| 152 | + base = "#{base}:#{requirement}" if requirement |
| 153 | + base |
| 154 | + end |
| 155 | + |
| 156 | + sig { params(original_requirement: T.nilable(String), new_version: String).returns(String) } |
| 157 | + def build_updated_requirement(original_requirement, new_version) |
| 158 | + return new_version unless original_requirement |
| 159 | + |
| 160 | + operator_match = original_requirement.match(/\A(?<op>[~^]|[><=]+)\s*/) |
| 161 | + if operator_match |
| 162 | + "#{operator_match[:op]}#{new_version}" |
| 163 | + else |
| 164 | + new_version |
| 165 | + end |
| 166 | + end |
| 167 | + end |
| 168 | + end |
| 169 | + end |
| 170 | +end |
| 171 | + |
| 172 | +Dependabot::PreCommit::AdditionalDependencyCheckers.register( |
| 173 | + "dart", |
| 174 | + Dependabot::PreCommit::AdditionalDependencyCheckers::Dart |
| 175 | +) |
0 commit comments