class Dependabot::Composer::FileParser

Constants

DEPENDENCY_GROUP_KEYS

Public Instance Methods

parse() click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 28
def parse
  dependency_set = DependencySet.new
  dependency_set += manifest_dependencies
  dependency_set += lockfile_dependencies
  dependency_set.dependencies
end

Private Instance Methods

build_lockfile_dependency(name, version, keys) click to toggle source

rubocop:enable Metrics/PerceivedComplexity

# File lib/dependabot/composer/file_parser.rb, line 109
def build_lockfile_dependency(name, version, keys)
  Dependency.new(
    name: name,
    version: version,
    requirements: [],
    package_manager: "composer",
    subdependency_metadata: [{
      production: keys.fetch(:group) != "development"
    }]
  )
end
build_manifest_dependency(name, req, keys) click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 63
def build_manifest_dependency(name, req, keys)
  Dependency.new(
    name: name,
    version: dependency_version(name: name, type: keys[:group]),
    requirements: [{
      requirement: req,
      file: "composer.json",
      source: dependency_source(
        name: name,
        type: keys[:group],
        requirement: req
      ),
      groups: [keys[:group]]
    }],
    package_manager: "composer"
  )
end
check_required_files() click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 185
def check_required_files
  raise "No composer.json!" unless get_original_file("composer.json")
end
composer_json() click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 203
def composer_json
  @composer_json ||= get_original_file("composer.json")
end
dependency_source(name:, type:, requirement:) click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 133
def dependency_source(name:, type:, requirement:)
  return unless lockfile

  package_details = lockfile_details(name: name, type: type)
  return unless package_details

  if package_details["source"].nil? &&
     package_details.dig("dist", "type") == "path"
    return { type: "path" }
  end

  git_dependency_details(package_details, requirement)
end
dependency_version(name:, type:) click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 121
def dependency_version(name:, type:)
  return unless lockfile

  package = lockfile_details(name: name, type: type)
  return unless package

  version = package.fetch("version")&.to_s&.sub(/^v?/, "")
  return version unless version&.start_with?("dev-")

  package.dig("source", "reference")
end
git_dependency_details(package_details, requirement) click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 147
def git_dependency_details(package_details, requirement)
  return unless package_details.dig("source", "type") == "git"

  branch =
    if requirement.start_with?("dev-")
      requirement.
        sub(/^dev-/, "").
        sub(/\s+as\s.*/, "").
        split("#").first
    elsif package_details.fetch("version")&.to_s&.start_with?("dev-")
      package_details.fetch("version")&.to_s&.sub(/^dev-/, "")
    end

  details = { type: "git", url: package_details.dig("source", "url") }
  return details unless branch

  details.merge(branch: branch, ref: nil)
end
lockfile() click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 207
def lockfile
  @lockfile ||= get_original_file("composer.lock")
end
lockfile_dependencies() click to toggle source

rubocop:disable Metrics/PerceivedComplexity

# File lib/dependabot/composer/file_parser.rb, line 82
def lockfile_dependencies
  dependencies = DependencySet.new

  return dependencies unless lockfile

  DEPENDENCY_GROUP_KEYS.each do |keys|
    key = keys.fetch(:lockfile)
    next unless parsed_lockfile[key].is_a?(Array)

    parsed_lockfile[key].each do |details|
      name = details["name"]
      next unless name.is_a?(String) && package?(name)

      version = details["version"]&.to_s&.sub(/^v?/, "")
      next unless version.is_a?(String)
      next unless version.match?(/^\d/) ||
                  version.match?(/^[0-9a-f]{40}$/)

      dependencies << build_lockfile_dependency(name, version, keys)
    end
  end

  dependencies
end
lockfile_details(name:, type:) click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 166
def lockfile_details(name:, type:)
  key = lockfile_key(type)
  parsed_lockfile.fetch(key, []).find { |d| d["name"] == name }
end
lockfile_key(type) click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 171
def lockfile_key(type)
  case type
  when "runtime" then "packages"
  when "development" then "packages-dev"
  else raise "unknown type #{type}"
  end
end
manifest_dependencies() click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 37
def manifest_dependencies
  dependencies = DependencySet.new

  DEPENDENCY_GROUP_KEYS.each do |keys|
    next unless parsed_composer_json[keys[:manifest]].is_a?(Hash)

    parsed_composer_json[keys[:manifest]].each do |name, req|
      next unless package?(name)

      if lockfile
        version = dependency_version(name: name, type: keys[:group])

        # Ignore dependency versions which don't appear in the
        # composer.lock or are non-numeric and not a git SHA, since they
        # can't be compared later in the process.
        next unless version&.match?(/^\d/) ||
                    version&.match?(/^[0-9a-f]{40}$/)
      end

      dependencies << build_manifest_dependency(name, req, keys)
    end
  end

  dependencies
end
package?(name) click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 179
def package?(name)
  # Filter out php, ext-, composer-plugin-api, and other special
  # packages which don't behave as normal
  name.split("/").count == 2
end
parsed_composer_json() click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 197
def parsed_composer_json
  @parsed_composer_json ||= JSON.parse(composer_json.content)
rescue JSON::ParserError
  raise Dependabot::DependencyFileNotParseable, composer_json.path
end
parsed_lockfile() click to toggle source
# File lib/dependabot/composer/file_parser.rb, line 189
def parsed_lockfile
  return unless lockfile

  @parsed_lockfile ||= JSON.parse(lockfile.content)
rescue JSON::ParserError
  raise Dependabot::DependencyFileNotParseable, lockfile.path
end