class Dependabot::Gradle::FileParser

Constants

DEPENDENCY_DECLARATION_REGEX
DEPENDENCY_SET_DECLARATION_REGEX
DEPENDENCY_SET_ENTRY_REGEX
PART
PLUGIN_BLOCK_DECLARATION_REGEX
PLUGIN_ID_REGEX
PROPERTY_REGEX
SUPPORTED_BUILD_FILE_NAMES
VSN_PART

Public Instance Methods

parse() click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 41
def parse
  dependency_set = DependencySet.new
  buildfiles.each do |buildfile|
    dependency_set += buildfile_dependencies(buildfile)
  end
  script_plugin_files.each do |plugin_file|
    dependency_set += buildfile_dependencies(plugin_file)
  end
  dependency_set.dependencies
end

Private Instance Methods

argument_from_string(string, arg_name) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 170
def argument_from_string(string, arg_name)
  string.
    match(map_value_regex(arg_name))&.
    named_captures&.
    fetch("value")
end
buildfile_dependencies(buildfile) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 58
def buildfile_dependencies(buildfile)
  dependency_set = DependencySet.new

  dependency_set += shortform_buildfile_dependencies(buildfile)
  dependency_set += keyword_arg_buildfile_dependencies(buildfile)
  dependency_set += dependency_set_dependencies(buildfile)
  dependency_set += plugin_dependencies(buildfile)

  dependency_set
end
buildfiles() click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 295
def buildfiles
  @buildfiles ||= dependency_files.select do |f|
    f.name.end_with?(*SUPPORTED_BUILD_FILE_NAMES)
  end
end
check_required_files() click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 312
def check_required_files
  raise "No build.gradle or build.gradle.kts!" unless original_file
end
closing_bracket_index(string) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 283
def closing_bracket_index(string)
  closes_required = 1

  string.chars.each_with_index do |char, index|
    closes_required += 1 if char == "{"
    closes_required -= 1 if char == "}"
    return index if closes_required.zero?
  end

  0
end
dependency_from(details_hash:, buildfile:, in_dependency_set: false) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 177
def dependency_from(details_hash:, buildfile:, in_dependency_set: false)
  group   = evaluated_value(details_hash[:group], buildfile)
  name    = evaluated_value(details_hash[:name], buildfile)
  version = evaluated_value(details_hash[:version], buildfile)
  extra_groups = details_hash[:extra_groups] || []

  dependency_name =
    if group == "plugins" then name
    else "#{group}:#{name}"
    end
  groups =
    if group == "plugins" then ["plugins"] + extra_groups
    else []
    end
  source =
    source_from(group, name, version)

  # If we can't evaluate a property they we won't be able to
  # update this dependency
  return if "#{dependency_name}:#{version}".match?(PROPERTY_REGEX)
  return unless Gradle::Version.correct?(version)

  Dependency.new(
    name: dependency_name,
    version: version,
    requirements: [{
      requirement: version,
      file: buildfile.name,
      source: source,
      groups: groups,
      metadata: dependency_metadata(details_hash, in_dependency_set)
    }],
    package_manager: "gradle"
  )
end
dependency_metadata(details_hash, in_dependency_set) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 226
def dependency_metadata(details_hash, in_dependency_set)
  version_property_name =
    details_hash[:version].
    match(PROPERTY_REGEX)&.
    named_captures&.fetch("property_name")

  return unless version_property_name || in_dependency_set

  metadata = {}
  metadata[:property_name] = version_property_name if version_property_name
  if in_dependency_set
    metadata[:dependency_set] = {
      group: details_hash[:group],
      version: details_hash[:version]
    }
  end
  metadata
end
dependency_set_dependencies(buildfile) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 104
def dependency_set_dependencies(buildfile)
  dependency_set = DependencySet.new

  dependency_set_blocks = []

  prepared_content(buildfile).scan(DEPENDENCY_SET_DECLARATION_REGEX) do
    mch = Regexp.last_match
    dependency_set_blocks <<
      {
        arguments: mch.named_captures.fetch("arguments"),
        block: mch.post_match[0..closing_bracket_index(mch.post_match)]
      }
  end

  dependency_set_blocks.each do |blk|
    group   = argument_from_string(blk[:arguments], "group")
    version = argument_from_string(blk[:arguments], "version")

    next unless group && version

    blk[:block].scan(DEPENDENCY_SET_ENTRY_REGEX).flatten.each do |name|
      dep = dependency_from(
        details_hash: { group: group, name: name, version: version },
        buildfile: buildfile,
        in_dependency_set: true
      )
      dependency_set << dep if dep
    end
  end

  dependency_set
end
evaluated_value(value, buildfile) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 245
def evaluated_value(value, buildfile)
  return value unless value.scan(PROPERTY_REGEX).count == 1

  property_name  = value.match(PROPERTY_REGEX).
                   named_captures.fetch("property_name")
  property_value = property_value_finder.property_value(
    property_name: property_name,
    callsite_buildfile: buildfile
  )

  return value unless property_value

  value.gsub(PROPERTY_REGEX, property_value)
end
extra_groups(line) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 166
def extra_groups(line)
  line.match(/kotlin(\s+#{PLUGIN_ID_REGEX}|\(#{PLUGIN_ID_REGEX}\))/) ? ["kotlin"] : []
end
keyword_arg_buildfile_dependencies(buildfile) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 86
def keyword_arg_buildfile_dependencies(buildfile)
  dependency_set = DependencySet.new

  prepared_content(buildfile).lines.each do |line|
    name    = argument_from_string(line, "name")
    group   = argument_from_string(line, "group")
    version = argument_from_string(line, "version")
    next unless name && group && version

    details = { name: name, group: group, version: version }

    dep = dependency_from(details_hash: details, buildfile: buildfile)
    dependency_set << dep if dep
  end

  dependency_set
end
map_value_regex(key) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 54
def map_value_regex(key)
  /(?:^|\s|,|\()#{Regexp.quote(key)}(\s*=|:)\s*['"](?<value>[^'"]+)['"]/
end
original_file() click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 316
def original_file
  dependency_files.find do |f|
    SUPPORTED_BUILD_FILE_NAMES.include?(f.name)
  end
end
plugin_dependencies(buildfile) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 137
def plugin_dependencies(buildfile)
  dependency_set = DependencySet.new

  plugin_blocks = []

  prepared_content(buildfile).scan(PLUGIN_BLOCK_DECLARATION_REGEX) do
    mch = Regexp.last_match
    plugin_blocks <<
      mch.post_match[0..closing_bracket_index(mch.post_match)]
  end

  plugin_blocks.each do |blk|
    blk.lines.each do |line|
      name_regex = /(id|kotlin)(\s+#{PLUGIN_ID_REGEX}|\(#{PLUGIN_ID_REGEX}\))/
      name = line.match(name_regex)&.named_captures&.fetch("id")
      version_regex = /version\s+['"](?<version>#{VSN_PART})['"]/
      version = line.match(version_regex)&.named_captures&.
          fetch("version")
      next unless name && version

      details = { name: name, group: "plugins", extra_groups: extra_groups(line), version: version }
      dep = dependency_from(details_hash: details, buildfile: buildfile)
      dependency_set << dep if dep
    end
  end

  dependency_set
end
prepared_content(buildfile) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 265
def prepared_content(buildfile)
  # Remove any comments
  prepared_content =
    buildfile.content.
    gsub(%r{(?<=^|\s)//.*$}, "\n").
    gsub(%r{(?<=^|\s)/\*.*?\*/}m, "")

  # Remove the dependencyVerification section added by Gradle Witness
  # (TODO: Support updating this in the FileUpdater)
  prepared_content.dup.scan(/dependencyVerification\s*{/) do
    mtch = Regexp.last_match
    block = mtch.post_match[0..closing_bracket_index(mtch.post_match)]
    prepared_content.gsub!(block, "")
  end

  prepared_content
end
property_value_finder() click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 260
def property_value_finder
  @property_value_finder ||=
    PropertyValueFinder.new(dependency_files: dependency_files)
end
script_plugin_files() click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 301
def script_plugin_files
  @script_plugin_files ||=
    buildfiles.flat_map do |buildfile|
      buildfile.content.
        scan(/apply from(\s+=|:)\s+['"]([^'"]+)['"]/).flatten.
        map { |f| dependency_files.find { |bf| bf.name == f } }.
        compact
    end.
    uniq
end
shortform_buildfile_dependencies(buildfile) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 69
def shortform_buildfile_dependencies(buildfile)
  dependency_set = DependencySet.new

  prepared_content(buildfile).scan(DEPENDENCY_DECLARATION_REGEX) do
    declaration = Regexp.last_match.named_captures.fetch("declaration")

    group, name, version = declaration.split(":")
    version, _packaging_type = version.split("@")
    details = { group: group, name: name, version: version }

    dep = dependency_from(details_hash: details, buildfile: buildfile)
    dependency_set << dep if dep
  end

  dependency_set
end
source_from(group, name, version) click to toggle source
# File lib/dependabot/gradle/file_parser.rb, line 213
def source_from(group, name, version)
  return nil unless group&.start_with?("com.github") && version.match?(/\A[0-9a-f]{40}\Z/)

  account = group.sub("com.github.", "")

  {
    type: "git",
    url: "https://github.com/#{account}/#{name}",
    branch: nil,
    ref: version
  }
end