dependabot-composer 0.398.0 → 0.400.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.
@@ -0,0 +1,146 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "time"
6
+ require "uri"
7
+ require "sorbet-runtime"
8
+ require "dependabot/errors"
9
+ require "dependabot/composer/document_value_parser"
10
+
11
+ module Dependabot
12
+ module Composer
13
+ module Package
14
+ class RegistryPackage < T::ImmutableStruct
15
+ extend T::Sig
16
+
17
+ class Release < T::ImmutableStruct
18
+ const :version_string, T.nilable(String)
19
+ const :released_at, T.nilable(Time)
20
+ const :url, T.nilable(String)
21
+ end
22
+
23
+ const :releases, T::Array[Release]
24
+
25
+ sig { params(content: String, package_name: String, source: String).returns(RegistryPackage) }
26
+ def self.from_json(content, package_name:, source:)
27
+ name = package_name.downcase
28
+ context = "Registry '#{source_label(source)}' package '#{name}'"
29
+ value = T.cast(JSON.parse(content), Object)
30
+ return new(releases: []) if value.nil? || value == []
31
+
32
+ fields = DocumentValueParser.object_hash(value, context)
33
+ entries = version_entries(fields["packages"], name, context)
34
+ return new(releases: []) if entries.empty?
35
+
36
+ new(releases: parse_releases(entries, minified: minified?(fields["minified"], context), context: context))
37
+ rescue JSON::ParserError
38
+ raise DependencyFileNotResolvable.new("#{context} must contain valid JSON"), cause: nil
39
+ rescue TypeError => e
40
+ raise DependencyFileNotResolvable.new(e.message), cause: nil
41
+ end
42
+
43
+ sig { params(source: String).returns(String) }
44
+ def self.source_label(source)
45
+ uri = URI.parse(source)
46
+ uri.userinfo = nil
47
+ uri.query = nil
48
+ uri.fragment = nil
49
+ String(uri)
50
+ end
51
+ private_class_method :source_label
52
+
53
+ sig { params(value: Object, name: String, context: String).returns(T::Array[Object]) }
54
+ def self.version_entries(value, name, context)
55
+ return [] if value.nil? || value == []
56
+
57
+ packages = DocumentValueParser.object_hash(value, "#{context} packages")
58
+ versions = packages[name]
59
+ case versions
60
+ when nil then []
61
+ when Hash then DocumentValueParser.object_hash(versions, "#{context} versions").values
62
+ when Array then versions.map { |entry| T.cast(entry, Object) }
63
+ else raise TypeError, "#{context} versions must be a map or an array"
64
+ end
65
+ end
66
+ private_class_method :version_entries
67
+
68
+ sig { params(value: Object, context: String).returns(T::Boolean) }
69
+ def self.minified?(value, context)
70
+ return false if value.nil?
71
+ return true if value == "composer/2.0"
72
+
73
+ raise TypeError, "#{context} minified must be composer/2.0 or nil"
74
+ end
75
+ private_class_method :minified?
76
+
77
+ sig { params(entries: T::Array[Object], minified: T::Boolean, context: String).returns(T::Array[Release]) }
78
+ def self.parse_releases(entries, minified:, context:)
79
+ previous = T.let(nil, T.nilable(DocumentValueParser::ObjectHash))
80
+ entries.each_with_index.map do |entry, index|
81
+ location = "#{context} releases[#{index}]"
82
+ fields = DocumentValueParser.object_hash(entry, location)
83
+ if minified
84
+ fields = expand_fields(previous, fields)
85
+ previous = fields
86
+ end
87
+ parse_release(fields, location)
88
+ end
89
+ end
90
+ private_class_method :parse_releases
91
+
92
+ sig do
93
+ params(previous: T.nilable(DocumentValueParser::ObjectHash), delta: DocumentValueParser::ObjectHash)
94
+ .returns(DocumentValueParser::ObjectHash)
95
+ end
96
+ def self.expand_fields(previous, delta)
97
+ return delta if previous.nil? || previous.empty?
98
+
99
+ # composer/2.0 deltas replace top-level fields, not nested members.
100
+ expanded = previous.dup
101
+ delta.each do |key, value|
102
+ if value == "__unset"
103
+ expanded.delete(key)
104
+ else
105
+ expanded[key] = value
106
+ end
107
+ end
108
+ expanded
109
+ end
110
+ private_class_method :expand_fields
111
+
112
+ sig { params(fields: DocumentValueParser::ObjectHash, context: String).returns(Release) }
113
+ def self.parse_release(fields, context)
114
+ raise TypeError, "#{context}.version is required" unless fields.key?("version")
115
+
116
+ Release.new(
117
+ version_string: DocumentValueParser.optional_string(fields["version"], "#{context}.version"),
118
+ released_at: parse_time(fields["time"], "#{context}.time"),
119
+ url: distribution_url(fields["dist"], "#{context}.dist")
120
+ )
121
+ end
122
+ private_class_method :parse_release
123
+
124
+ sig { params(value: Object, context: String).returns(T.nilable(Time)) }
125
+ def self.parse_time(value, context)
126
+ string = DocumentValueParser.optional_string(value, context)
127
+ return if string.nil?
128
+
129
+ Time.parse(string)
130
+ rescue ArgumentError
131
+ raise TypeError.new("#{context} must be a valid timestamp"), cause: nil
132
+ end
133
+ private_class_method :parse_time
134
+
135
+ sig { params(value: Object, context: String).returns(T.nilable(String)) }
136
+ def self.distribution_url(value, context)
137
+ return if value.nil?
138
+
139
+ fields = DocumentValueParser.object_hash(value, context)
140
+ DocumentValueParser.optional_string(fields["url"], "#{context}.url")
141
+ end
142
+ private_class_method :distribution_url
143
+ end
144
+ end
145
+ end
146
+ end
@@ -519,16 +519,17 @@ module Dependabot
519
519
 
520
520
  sig { returns(String) }
521
521
  def composer_version
522
- parsed_lockfile_or_nil = lockfile ? parsed_lockfile : nil
522
+ lockfile_document =
523
+ (LockfileDocument.new(data: parsed_lockfile, context: T.must(lockfile).path) if lockfile)
523
524
  @composer_version ||= T.let(
524
- Helpers.composer_version(parsed_composer_file, parsed_lockfile_or_nil),
525
+ Helpers.composer_version(manifest_document, lockfile_document),
525
526
  T.nilable(String)
526
527
  )
527
528
  end
528
529
 
529
530
  sig { returns(T::Hash[String, T::Array[String]]) }
530
531
  def initial_platform
531
- platform_php = Helpers.capture_platform_php(parsed_composer_file)
532
+ platform_php = Helpers.capture_platform_php(manifest_document)
532
533
 
533
534
  platform = {}
534
535
  if platform_php.is_a?(String) && requirement_valid?(platform_php)
@@ -538,7 +539,7 @@ module Dependabot
538
539
  # NOTE: We *don't* include the require-dev PHP version in our initial
539
540
  # platform. If we fail to resolve with the PHP version specified in
540
541
  # `require` then it will be picked up in a subsequent iteration.
541
- requirement_php = Helpers.php_constraint(parsed_composer_file)
542
+ requirement_php = Helpers.php_constraint(manifest_document)
542
543
  return platform unless requirement_php.is_a?(String)
543
544
  return platform unless requirement_valid?(requirement_php)
544
545
 
@@ -547,6 +548,14 @@ module Dependabot
547
548
  platform
548
549
  end
549
550
 
551
+ sig { returns(ManifestDocument) }
552
+ def manifest_document
553
+ @manifest_document ||= T.let(
554
+ ManifestDocument.new(data: parsed_composer_file, context: T.must(composer_file).path),
555
+ T.nilable(ManifestDocument)
556
+ )
557
+ end
558
+
550
559
  sig { returns(T::Hash[String, T.untyped]) }
551
560
  def parsed_composer_file
552
561
  @parsed_composer_file ||= T.let(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-composer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.398.0
4
+ version: 0.400.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.398.0
18
+ version: 0.400.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.398.0
25
+ version: 0.400.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -254,6 +254,7 @@ files:
254
254
  - helpers/v2/src/Updater.php
255
255
  - lib/dependabot/composer.rb
256
256
  - lib/dependabot/composer/composer_error_handler.rb
257
+ - lib/dependabot/composer/document_value_parser.rb
257
258
  - lib/dependabot/composer/file_fetcher.rb
258
259
  - lib/dependabot/composer/file_fetcher/path_dependency_builder.rb
259
260
  - lib/dependabot/composer/file_parser.rb
@@ -262,9 +263,12 @@ files:
262
263
  - lib/dependabot/composer/file_updater/manifest_updater.rb
263
264
  - lib/dependabot/composer/helpers.rb
264
265
  - lib/dependabot/composer/language.rb
266
+ - lib/dependabot/composer/lockfile_document.rb
267
+ - lib/dependabot/composer/manifest_document.rb
265
268
  - lib/dependabot/composer/metadata_finder.rb
266
269
  - lib/dependabot/composer/native_helpers.rb
267
270
  - lib/dependabot/composer/package/package_details_fetcher.rb
271
+ - lib/dependabot/composer/package/registry_package.rb
268
272
  - lib/dependabot/composer/package_manager.rb
269
273
  - lib/dependabot/composer/requirement.rb
270
274
  - lib/dependabot/composer/update_checker.rb
@@ -277,7 +281,7 @@ licenses:
277
281
  - MIT
278
282
  metadata:
279
283
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
280
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.398.0
284
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.400.0
281
285
  rdoc_options: []
282
286
  require_paths:
283
287
  - lib