dependabot-pre_commit 0.361.2
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 +7 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/base.rb +89 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/go.rb +160 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/node.rb +175 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/python.rb +213 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/ruby.rb +169 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers/rust.rb +179 -0
- data/lib/dependabot/pre_commit/additional_dependency_checkers.rb +46 -0
- data/lib/dependabot/pre_commit/file_fetcher.rb +70 -0
- data/lib/dependabot/pre_commit/file_parser.rb +210 -0
- data/lib/dependabot/pre_commit/file_updater.rb +188 -0
- data/lib/dependabot/pre_commit/helpers.rb +82 -0
- data/lib/dependabot/pre_commit/metadata_finder.rb +28 -0
- data/lib/dependabot/pre_commit/package/package_details_fetcher.rb +199 -0
- data/lib/dependabot/pre_commit/package_manager.rb +29 -0
- data/lib/dependabot/pre_commit/requirement.rb +23 -0
- data/lib/dependabot/pre_commit/update_checker/latest_version_finder.rb +240 -0
- data/lib/dependabot/pre_commit/update_checker.rb +284 -0
- data/lib/dependabot/pre_commit/version.rb +15 -0
- data/lib/dependabot/pre_commit.rb +20 -0
- metadata +356 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "excon"
|
|
5
|
+
require "json"
|
|
6
|
+
require "sorbet-runtime"
|
|
7
|
+
require "dependabot/dependency"
|
|
8
|
+
require "dependabot/update_checkers"
|
|
9
|
+
require "dependabot/pre_commit/additional_dependency_checkers"
|
|
10
|
+
require "dependabot/pre_commit/additional_dependency_checkers/base"
|
|
11
|
+
|
|
12
|
+
module Dependabot
|
|
13
|
+
module PreCommit
|
|
14
|
+
module AdditionalDependencyCheckers
|
|
15
|
+
class Ruby < Base
|
|
16
|
+
extend T::Sig
|
|
17
|
+
|
|
18
|
+
sig { override.returns(T.nilable(String)) }
|
|
19
|
+
def latest_version
|
|
20
|
+
return nil unless package_name
|
|
21
|
+
|
|
22
|
+
@latest_version ||= T.let(
|
|
23
|
+
fetch_latest_version_via_bundler_checker,
|
|
24
|
+
T.nilable(String)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
sig { override.params(latest_version: String).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
29
|
+
def updated_requirements(latest_version)
|
|
30
|
+
requirements.map do |original_req|
|
|
31
|
+
original_source = original_req[:source]
|
|
32
|
+
next original_req unless original_source.is_a?(Hash)
|
|
33
|
+
next original_req unless original_source[:type] == "additional_dependency"
|
|
34
|
+
|
|
35
|
+
original_requirement = original_req[:requirement]
|
|
36
|
+
new_requirement = build_updated_requirement(original_requirement, latest_version)
|
|
37
|
+
|
|
38
|
+
new_original_string = build_original_string(
|
|
39
|
+
package_name: original_source[:original_name] || original_source[:package_name],
|
|
40
|
+
requirement: new_requirement
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
new_source = original_source.merge(original_string: new_original_string)
|
|
44
|
+
|
|
45
|
+
original_req.merge(
|
|
46
|
+
requirement: new_requirement,
|
|
47
|
+
source: new_source
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
sig { returns(T.nilable(String)) }
|
|
55
|
+
def fetch_latest_version_via_bundler_checker
|
|
56
|
+
bundler_checker = bundler_update_checker
|
|
57
|
+
return nil unless bundler_checker
|
|
58
|
+
|
|
59
|
+
latest = bundler_checker.latest_version
|
|
60
|
+
Dependabot.logger.info("Ruby UpdateChecker found latest version: #{latest || 'none'}")
|
|
61
|
+
|
|
62
|
+
latest&.to_s
|
|
63
|
+
rescue Dependabot::DependabotError, Excon::Error, JSON::ParserError => e
|
|
64
|
+
Dependabot.logger.debug("Error checking Ruby gem #{package_name}: #{e.message}")
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
69
|
+
def bundler_update_checker
|
|
70
|
+
@bundler_update_checker ||= T.let(
|
|
71
|
+
build_bundler_update_checker,
|
|
72
|
+
T.nilable(Dependabot::UpdateCheckers::Base)
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
77
|
+
def build_bundler_update_checker
|
|
78
|
+
bundler_dependency = build_bundler_dependency
|
|
79
|
+
return nil unless bundler_dependency
|
|
80
|
+
|
|
81
|
+
Dependabot.logger.info("Delegating to bundler UpdateChecker for gem: #{bundler_dependency.name}")
|
|
82
|
+
|
|
83
|
+
Dependabot::UpdateCheckers.for_package_manager("bundler").new(
|
|
84
|
+
dependency: bundler_dependency,
|
|
85
|
+
dependency_files: build_bundler_dependency_files,
|
|
86
|
+
credentials: credentials,
|
|
87
|
+
ignored_versions: [],
|
|
88
|
+
security_advisories: [],
|
|
89
|
+
raise_on_ignored: false
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
sig { returns(T.nilable(Dependabot::Dependency)) }
|
|
94
|
+
def build_bundler_dependency
|
|
95
|
+
return nil unless package_name
|
|
96
|
+
|
|
97
|
+
version = current_version || extract_version_from_requirement
|
|
98
|
+
|
|
99
|
+
Dependabot::Dependency.new(
|
|
100
|
+
name: T.must(package_name),
|
|
101
|
+
version: version,
|
|
102
|
+
requirements: [{
|
|
103
|
+
requirement: version ? "= #{version}" : nil,
|
|
104
|
+
groups: [],
|
|
105
|
+
file: "Gemfile",
|
|
106
|
+
source: nil
|
|
107
|
+
}],
|
|
108
|
+
package_manager: "bundler"
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
sig { returns(T.nilable(String)) }
|
|
113
|
+
def extract_version_from_requirement
|
|
114
|
+
req_string = requirements.first&.dig(:requirement)
|
|
115
|
+
return nil unless req_string
|
|
116
|
+
|
|
117
|
+
version_part = req_string.sub(/\A[~><=!]+\s*/, "").strip
|
|
118
|
+
return version_part if version_part.match?(/\A\d+(?:\.\d+)*(?:\.\w+)?\z/)
|
|
119
|
+
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
124
|
+
def build_bundler_dependency_files
|
|
125
|
+
version = current_version || extract_version_from_requirement
|
|
126
|
+
version_constraint = version ? ", \"#{version}\"" : ""
|
|
127
|
+
gemfile_content = "source 'https://rubygems.org'\ngem '#{package_name}'#{version_constraint}\n"
|
|
128
|
+
|
|
129
|
+
[
|
|
130
|
+
Dependabot::DependencyFile.new(
|
|
131
|
+
name: "Gemfile",
|
|
132
|
+
content: gemfile_content
|
|
133
|
+
)
|
|
134
|
+
]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
sig do
|
|
138
|
+
params(
|
|
139
|
+
package_name: T.nilable(String),
|
|
140
|
+
requirement: T.nilable(String)
|
|
141
|
+
).returns(String)
|
|
142
|
+
end
|
|
143
|
+
def build_original_string(package_name:, requirement:)
|
|
144
|
+
base = package_name.to_s
|
|
145
|
+
base = "#{base}:#{requirement}" if requirement
|
|
146
|
+
base
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
sig { params(original_requirement: T.nilable(String), new_version: String).returns(String) }
|
|
150
|
+
def build_updated_requirement(original_requirement, new_version)
|
|
151
|
+
return new_version unless original_requirement
|
|
152
|
+
|
|
153
|
+
# Handle Ruby gem version operators: ~>, >=, >, <=, <, =, !=
|
|
154
|
+
operator_match = original_requirement.match(/\A(?<op>[~><=!]+)\s*/)
|
|
155
|
+
if operator_match
|
|
156
|
+
"#{operator_match[:op]} #{new_version}"
|
|
157
|
+
else
|
|
158
|
+
new_version
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers.register(
|
|
167
|
+
"ruby",
|
|
168
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers::Ruby
|
|
169
|
+
)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "excon"
|
|
5
|
+
require "json"
|
|
6
|
+
require "sorbet-runtime"
|
|
7
|
+
require "dependabot/dependency"
|
|
8
|
+
require "dependabot/update_checkers"
|
|
9
|
+
require "dependabot/pre_commit/additional_dependency_checkers"
|
|
10
|
+
require "dependabot/pre_commit/additional_dependency_checkers/base"
|
|
11
|
+
|
|
12
|
+
module Dependabot
|
|
13
|
+
module PreCommit
|
|
14
|
+
module AdditionalDependencyCheckers
|
|
15
|
+
class Rust < Base
|
|
16
|
+
extend T::Sig
|
|
17
|
+
|
|
18
|
+
sig { override.returns(T.nilable(String)) }
|
|
19
|
+
def latest_version
|
|
20
|
+
return nil unless package_name
|
|
21
|
+
|
|
22
|
+
@latest_version ||= T.let(
|
|
23
|
+
fetch_latest_version_via_cargo_checker,
|
|
24
|
+
T.nilable(String)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
sig { override.params(latest_version: String).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
29
|
+
def updated_requirements(latest_version)
|
|
30
|
+
requirements.map do |original_req|
|
|
31
|
+
original_source = original_req[:source]
|
|
32
|
+
next original_req unless original_source.is_a?(Hash)
|
|
33
|
+
next original_req unless original_source[:type] == "additional_dependency"
|
|
34
|
+
|
|
35
|
+
original_requirement = original_req[:requirement]
|
|
36
|
+
new_requirement = build_updated_requirement(original_requirement, latest_version)
|
|
37
|
+
|
|
38
|
+
new_original_string = build_original_string(
|
|
39
|
+
package_name: original_source[:original_name] || original_source[:package_name],
|
|
40
|
+
requirement: new_requirement,
|
|
41
|
+
cli: original_source[:extras] == "cli"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
new_source = original_source.merge(original_string: new_original_string)
|
|
45
|
+
|
|
46
|
+
original_req.merge(
|
|
47
|
+
requirement: new_requirement,
|
|
48
|
+
source: new_source
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
sig { returns(T.nilable(String)) }
|
|
56
|
+
def fetch_latest_version_via_cargo_checker
|
|
57
|
+
cargo_checker = cargo_update_checker
|
|
58
|
+
return nil unless cargo_checker
|
|
59
|
+
|
|
60
|
+
latest = cargo_checker.latest_version
|
|
61
|
+
Dependabot.logger.info("Cargo UpdateChecker found latest version: #{latest || 'none'}")
|
|
62
|
+
|
|
63
|
+
latest&.to_s
|
|
64
|
+
rescue Dependabot::DependabotError, Excon::Error, JSON::ParserError => e
|
|
65
|
+
Dependabot.logger.debug("Error checking Rust package #{package_name}: #{e.message}")
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
70
|
+
def cargo_update_checker
|
|
71
|
+
@cargo_update_checker ||= T.let(
|
|
72
|
+
build_cargo_update_checker,
|
|
73
|
+
T.nilable(Dependabot::UpdateCheckers::Base)
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
sig { returns(T.nilable(Dependabot::UpdateCheckers::Base)) }
|
|
78
|
+
def build_cargo_update_checker
|
|
79
|
+
cargo_dependency = build_cargo_dependency
|
|
80
|
+
return nil unless cargo_dependency
|
|
81
|
+
|
|
82
|
+
Dependabot.logger.info("Delegating to cargo UpdateChecker for package: #{cargo_dependency.name}")
|
|
83
|
+
|
|
84
|
+
Dependabot::UpdateCheckers.for_package_manager("cargo").new(
|
|
85
|
+
dependency: cargo_dependency,
|
|
86
|
+
dependency_files: build_cargo_dependency_files,
|
|
87
|
+
credentials: credentials,
|
|
88
|
+
ignored_versions: [],
|
|
89
|
+
security_advisories: [],
|
|
90
|
+
raise_on_ignored: false
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
sig { returns(T.nilable(Dependabot::Dependency)) }
|
|
95
|
+
def build_cargo_dependency
|
|
96
|
+
return nil unless package_name
|
|
97
|
+
|
|
98
|
+
version = current_version || extract_version_from_requirement
|
|
99
|
+
|
|
100
|
+
Dependabot::Dependency.new(
|
|
101
|
+
name: T.must(package_name),
|
|
102
|
+
version: version,
|
|
103
|
+
requirements: [{
|
|
104
|
+
requirement: version ? "=#{version}" : nil,
|
|
105
|
+
groups: ["dependencies"],
|
|
106
|
+
file: "Cargo.toml",
|
|
107
|
+
source: nil
|
|
108
|
+
}],
|
|
109
|
+
package_manager: "cargo"
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
sig { returns(T.nilable(String)) }
|
|
114
|
+
def extract_version_from_requirement
|
|
115
|
+
req_string = requirements.first&.dig(:requirement)
|
|
116
|
+
return nil unless req_string
|
|
117
|
+
|
|
118
|
+
version_part = req_string.sub(/\A(?:[~^]|[><=]+)\s*/, "")
|
|
119
|
+
return version_part if version_part.match?(/\A\d+(?:\.\d+)*(?:-[\w.]+)?(?:\+[\w.]+)?\z/)
|
|
120
|
+
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
125
|
+
def build_cargo_dependency_files
|
|
126
|
+
version = current_version || extract_version_from_requirement
|
|
127
|
+
requirement = version ? "=#{version}" : "*"
|
|
128
|
+
|
|
129
|
+
toml_content = <<~TOML
|
|
130
|
+
[package]
|
|
131
|
+
name = "dependabot-pre-commit-check"
|
|
132
|
+
version = "0.0.1"
|
|
133
|
+
edition = "2021"
|
|
134
|
+
|
|
135
|
+
[dependencies]
|
|
136
|
+
#{T.must(package_name)} = "#{requirement}"
|
|
137
|
+
TOML
|
|
138
|
+
|
|
139
|
+
[
|
|
140
|
+
Dependabot::DependencyFile.new(
|
|
141
|
+
name: "Cargo.toml",
|
|
142
|
+
content: toml_content
|
|
143
|
+
)
|
|
144
|
+
]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
sig do
|
|
148
|
+
params(
|
|
149
|
+
package_name: T.nilable(String),
|
|
150
|
+
requirement: T.nilable(String),
|
|
151
|
+
cli: T::Boolean
|
|
152
|
+
).returns(String)
|
|
153
|
+
end
|
|
154
|
+
def build_original_string(package_name:, requirement:, cli:)
|
|
155
|
+
base = cli ? "cli:#{package_name}" : package_name.to_s
|
|
156
|
+
base = "#{base}:#{requirement}" if requirement
|
|
157
|
+
base
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
sig { params(original_requirement: T.nilable(String), new_version: String).returns(String) }
|
|
161
|
+
def build_updated_requirement(original_requirement, new_version)
|
|
162
|
+
return new_version unless original_requirement
|
|
163
|
+
|
|
164
|
+
operator_match = original_requirement.match(/\A(?<op>[~^]|[><=]+)\s*/)
|
|
165
|
+
if operator_match
|
|
166
|
+
"#{operator_match[:op]}#{new_version}"
|
|
167
|
+
else
|
|
168
|
+
new_version
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers.register(
|
|
177
|
+
"rust",
|
|
178
|
+
Dependabot::PreCommit::AdditionalDependencyCheckers::Rust
|
|
179
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/pre_commit/additional_dependency_checkers/base"
|
|
6
|
+
|
|
7
|
+
module Dependabot
|
|
8
|
+
module PreCommit
|
|
9
|
+
# Registry for additional_dependency update checkers by language.
|
|
10
|
+
# Similar pattern to Dependabot::UpdateCheckers but for pre-commit hook languages.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# checker_class = AdditionalDependencyCheckers.for_language("python")
|
|
14
|
+
# checker = checker_class.new(source: source, credentials: credentials, ...)
|
|
15
|
+
# latest_version = checker.latest_version
|
|
16
|
+
#
|
|
17
|
+
module AdditionalDependencyCheckers
|
|
18
|
+
extend T::Sig
|
|
19
|
+
|
|
20
|
+
@checkers = T.let({}, T::Hash[String, T.class_of(Base)])
|
|
21
|
+
|
|
22
|
+
sig { params(language: String).returns(T.class_of(Base)) }
|
|
23
|
+
def self.for_language(language)
|
|
24
|
+
checker = @checkers[language.downcase]
|
|
25
|
+
return checker if checker
|
|
26
|
+
|
|
27
|
+
raise "Unsupported language for additional_dependencies: #{language}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { params(language: String, checker: T.class_of(Base)).void }
|
|
31
|
+
def self.register(language, checker)
|
|
32
|
+
@checkers[language.downcase] = checker
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
sig { params(language: String).returns(T::Boolean) }
|
|
36
|
+
def self.supported?(language)
|
|
37
|
+
@checkers.key?(language.downcase)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
sig { returns(T::Array[String]) }
|
|
41
|
+
def self.supported_languages
|
|
42
|
+
@checkers.keys
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "dependabot/file_fetchers"
|
|
5
|
+
require "dependabot/file_fetchers/base"
|
|
6
|
+
require "dependabot/file_filtering"
|
|
7
|
+
|
|
8
|
+
module Dependabot
|
|
9
|
+
module PreCommit
|
|
10
|
+
class FileFetcher < Dependabot::FileFetchers::Base
|
|
11
|
+
extend T::Sig
|
|
12
|
+
|
|
13
|
+
CONFIG_FILE_PATTERN = /\.pre-commit(-config)?\.ya?ml$/i
|
|
14
|
+
|
|
15
|
+
sig { override.returns(String) }
|
|
16
|
+
def self.required_files_message
|
|
17
|
+
"Repo must contain a .pre-commit-config.yaml, .pre-commit-config.yml, " \
|
|
18
|
+
".pre-commit.yaml, or .pre-commit.yml file."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
|
|
22
|
+
def self.required_files_in?(filenames)
|
|
23
|
+
filenames.any? { |f| f.match?(CONFIG_FILE_PATTERN) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
sig { override.returns(T::Array[DependencyFile]) }
|
|
27
|
+
def fetch_files
|
|
28
|
+
unless allow_beta_ecosystems?
|
|
29
|
+
raise Dependabot::DependencyFileNotFound.new(
|
|
30
|
+
nil,
|
|
31
|
+
"PreCommit support is currently in beta. Set ALLOW_BETA_ECOSYSTEMS=true to enable it."
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
fetched_files = []
|
|
36
|
+
fetched_files << pre_commit_config
|
|
37
|
+
|
|
38
|
+
fetched_files.reject do |file|
|
|
39
|
+
Dependabot::FileFiltering.should_exclude_path?(file.name, "file from final collection", @exclude_paths)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
sig { override.returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
|
44
|
+
def ecosystem_versions
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
sig { returns(Dependabot::DependencyFile) }
|
|
51
|
+
def pre_commit_config
|
|
52
|
+
@pre_commit_config ||= T.let(
|
|
53
|
+
fetch_file_from_host(config_file_name),
|
|
54
|
+
T.nilable(Dependabot::DependencyFile)
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
sig { returns(String) }
|
|
59
|
+
def config_file_name
|
|
60
|
+
@config_file_name ||= T.let(
|
|
61
|
+
repo_contents.find { |f| f.name.match?(CONFIG_FILE_PATTERN) }&.name ||
|
|
62
|
+
".pre-commit-config.yaml",
|
|
63
|
+
T.nilable(String)
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
Dependabot::FileFetchers.register("pre_commit", Dependabot::PreCommit::FileFetcher)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "sorbet-runtime"
|
|
6
|
+
require "dependabot/dependency"
|
|
7
|
+
require "dependabot/file_parsers"
|
|
8
|
+
require "dependabot/file_parsers/base"
|
|
9
|
+
require "dependabot/errors"
|
|
10
|
+
require "dependabot/pre_commit/package_manager"
|
|
11
|
+
require "dependabot/pre_commit/version"
|
|
12
|
+
require "dependabot/pre_commit/requirement"
|
|
13
|
+
require "dependabot/cargo/requirement"
|
|
14
|
+
require "dependabot/npm_and_yarn/requirement"
|
|
15
|
+
require "dependabot/python/requirement_parser"
|
|
16
|
+
require "dependabot/bundler/requirement"
|
|
17
|
+
require "dependabot/go_modules/requirement_parser"
|
|
18
|
+
|
|
19
|
+
module Dependabot
|
|
20
|
+
module PreCommit
|
|
21
|
+
class FileParser < Dependabot::FileParsers::Base
|
|
22
|
+
extend T::Sig
|
|
23
|
+
|
|
24
|
+
require "dependabot/file_parsers/base/dependency_set"
|
|
25
|
+
|
|
26
|
+
CONFIG_FILE_PATTERN = /\.pre-commit(-config)?\.ya?ml$/i
|
|
27
|
+
ECOSYSTEM = "pre_commit"
|
|
28
|
+
|
|
29
|
+
LANGUAGE_PARSERS = T.let(
|
|
30
|
+
{
|
|
31
|
+
"python" => ->(dep_string) { Dependabot::Python::RequirementParser.parse(dep_string) },
|
|
32
|
+
"node" => ->(dep_string) { Dependabot::NpmAndYarn::Requirement.parse_dep_string(dep_string) },
|
|
33
|
+
"rust" => ->(dep_string) { Dependabot::Cargo::Requirement.parse_dep_string(dep_string) },
|
|
34
|
+
"golang" => ->(dep_string) { Dependabot::GoModules::RequirementParser.parse(dep_string) },
|
|
35
|
+
"ruby" => ->(dep_string) { Dependabot::Bundler::Requirement.parse_dep_string(dep_string) }
|
|
36
|
+
}.freeze,
|
|
37
|
+
T::Hash[String, T.proc.params(dep_string: String).returns(T.nilable(T::Hash[Symbol, T.untyped]))]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
sig { override.returns(Ecosystem) }
|
|
41
|
+
def ecosystem
|
|
42
|
+
@ecosystem ||= T.let(
|
|
43
|
+
Ecosystem.new(
|
|
44
|
+
name: ECOSYSTEM,
|
|
45
|
+
package_manager: package_manager
|
|
46
|
+
),
|
|
47
|
+
T.nilable(Ecosystem)
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
sig { override.returns(T::Array[Dependabot::Dependency]) }
|
|
52
|
+
def parse
|
|
53
|
+
dependency_set = DependencySet.new
|
|
54
|
+
|
|
55
|
+
pre_commit_config_files.each do |file|
|
|
56
|
+
dependency_set += parse_config_file(file)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
dependency_set.dependencies
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
sig { returns(Ecosystem::VersionManager) }
|
|
65
|
+
def package_manager
|
|
66
|
+
@package_manager ||= T.let(PackageManager.new, T.nilable(Dependabot::PreCommit::PackageManager))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
sig { params(file: Dependabot::DependencyFile).returns(DependencySet) }
|
|
70
|
+
def parse_config_file(file)
|
|
71
|
+
dependency_set = DependencySet.new
|
|
72
|
+
|
|
73
|
+
yaml = YAML.safe_load(T.must(file.content), aliases: true)
|
|
74
|
+
return dependency_set unless yaml.is_a?(Hash)
|
|
75
|
+
|
|
76
|
+
repos = yaml.fetch("repos", [])
|
|
77
|
+
repos.each do |repo|
|
|
78
|
+
next unless repo.is_a?(Hash)
|
|
79
|
+
|
|
80
|
+
dependency = parse_repo(repo, file)
|
|
81
|
+
dependency_set << dependency if dependency
|
|
82
|
+
|
|
83
|
+
additional_deps = parse_additional_dependencies(repo, file)
|
|
84
|
+
additional_deps.each { |dep| dependency_set << dep }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
dependency_set
|
|
88
|
+
rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::BadAlias => e
|
|
89
|
+
raise Dependabot::DependencyFileNotParseable.new(file.path, e.message)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
sig do
|
|
93
|
+
params(
|
|
94
|
+
repo: T::Hash[String, T.untyped],
|
|
95
|
+
file: Dependabot::DependencyFile
|
|
96
|
+
).returns(T.nilable(Dependency))
|
|
97
|
+
end
|
|
98
|
+
def parse_repo(repo, file)
|
|
99
|
+
repo_url = repo["repo"]
|
|
100
|
+
rev = repo["rev"]
|
|
101
|
+
|
|
102
|
+
return nil if repo_url.nil? || rev.nil?
|
|
103
|
+
return nil if %w(local meta).include?(repo_url)
|
|
104
|
+
|
|
105
|
+
Dependency.new(
|
|
106
|
+
name: repo_url,
|
|
107
|
+
version: rev,
|
|
108
|
+
requirements: [{
|
|
109
|
+
requirement: nil,
|
|
110
|
+
groups: [],
|
|
111
|
+
file: file.name,
|
|
112
|
+
source: {
|
|
113
|
+
type: "git",
|
|
114
|
+
url: repo_url,
|
|
115
|
+
ref: rev,
|
|
116
|
+
branch: nil
|
|
117
|
+
}
|
|
118
|
+
}],
|
|
119
|
+
package_manager: ECOSYSTEM
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
sig do
|
|
124
|
+
params(
|
|
125
|
+
repo: T::Hash[String, T.untyped],
|
|
126
|
+
file: Dependabot::DependencyFile
|
|
127
|
+
).returns(T::Array[Dependabot::Dependency])
|
|
128
|
+
end
|
|
129
|
+
def parse_additional_dependencies(repo, file)
|
|
130
|
+
dependencies = []
|
|
131
|
+
repo_url = repo["repo"]
|
|
132
|
+
|
|
133
|
+
return dependencies if repo_url.nil? || %w(local meta).include?(repo_url)
|
|
134
|
+
|
|
135
|
+
hooks = repo.fetch("hooks", [])
|
|
136
|
+
hooks.each do |hook|
|
|
137
|
+
next unless hook.is_a?(Hash)
|
|
138
|
+
|
|
139
|
+
hook_deps = parse_hook_additional_dependencies(hook, repo_url, file)
|
|
140
|
+
dependencies.concat(hook_deps)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
dependencies
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
sig do
|
|
147
|
+
params(
|
|
148
|
+
hook: T::Hash[String, T.untyped],
|
|
149
|
+
repo_url: String,
|
|
150
|
+
file: Dependabot::DependencyFile
|
|
151
|
+
).returns(T::Array[Dependabot::Dependency])
|
|
152
|
+
end
|
|
153
|
+
def parse_hook_additional_dependencies(hook, repo_url, file)
|
|
154
|
+
dependencies = []
|
|
155
|
+
|
|
156
|
+
return dependencies unless hook["id"]
|
|
157
|
+
|
|
158
|
+
additional_deps = hook.fetch("additional_dependencies", [])
|
|
159
|
+
return dependencies if additional_deps.empty?
|
|
160
|
+
|
|
161
|
+
parser = LANGUAGE_PARSERS[hook["language"]]
|
|
162
|
+
return dependencies unless parser
|
|
163
|
+
|
|
164
|
+
additional_deps.each do |dep_string|
|
|
165
|
+
next unless dep_string.is_a?(String)
|
|
166
|
+
|
|
167
|
+
parsed = parser.call(dep_string)
|
|
168
|
+
next unless parsed
|
|
169
|
+
|
|
170
|
+
dependencies << Dependabot::Dependency.new(
|
|
171
|
+
name: parsed[:normalised_name],
|
|
172
|
+
version: parsed[:version],
|
|
173
|
+
requirements: [{
|
|
174
|
+
requirement: parsed[:requirement],
|
|
175
|
+
groups: ["additional_dependencies"],
|
|
176
|
+
file: file.name,
|
|
177
|
+
source: {
|
|
178
|
+
type: "additional_dependency",
|
|
179
|
+
language: hook["language"],
|
|
180
|
+
package_name: parsed[:normalised_name],
|
|
181
|
+
original_name: parsed[:name],
|
|
182
|
+
hook_id: hook["id"],
|
|
183
|
+
hook_repo: repo_url,
|
|
184
|
+
extras: parsed[:extras],
|
|
185
|
+
original_string: dep_string
|
|
186
|
+
}
|
|
187
|
+
}],
|
|
188
|
+
package_manager: ECOSYSTEM
|
|
189
|
+
)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
dependencies
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
196
|
+
def pre_commit_config_files
|
|
197
|
+
dependency_files.select { |f| f.name.match?(CONFIG_FILE_PATTERN) }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
sig { override.void }
|
|
201
|
+
def check_required_files
|
|
202
|
+
return if pre_commit_config_files.any?
|
|
203
|
+
|
|
204
|
+
raise "No pre-commit configuration file found!"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
Dependabot::FileParsers.register("pre_commit", Dependabot::PreCommit::FileParser)
|