Class: Dependabot::Julia::Requirement

Inherits:
Requirement
  • Object
show all
Defined in:
lib/dependabot/julia/requirement.rb

Constant Summary collapse

HYPHEN_RANGE_PATTERN =

Julia hyphen ranges require whitespace around the hyphen ("1.2 - 3.4"); without spaces the hyphen introduces a prerelease tag instead.

T.let(/^(\d+(?:\.\d+)*)\s+-\s+(\d+(?:\.\d+)*)$/, Regexp)

Class Method Summary collapse

Class Method Details

.compound_constraint?(constraints) ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/dependabot/julia/requirement.rb', line 32

def self.compound_constraint?(constraints)
  # Compound constraints (e.g., ">= 1.0, < 2.0") are when explicit comparison operators
  # (>=, <=, <, >, =) work together to define a single range.
  # Separate constraints (e.g., "^1.10, 2" or "0.34, 0.35") use version specs
  # (with or without ^/~) as OR conditions - any matching spec is acceptable.
  # Only treat as compound if ALL constraints use explicit comparison operators.
  #
  # NOTE: Julia's Pkg unions *all* comma-separated compat specs, including
  # operator-style ones, so ">= 1.0, < 2.0" is `*` to the resolver. We keep
  # intersection semantics for operator-style lists anyway because this
  # method also parses Dependabot ignore conditions (e.g. ">= 2.a, < 3"),
  # which are always intersections; treating those as unions would make
  # every ignore condition match all versions.
  return false if constraints.length <= 1

  constraints.all? { |c| c.match?(/^[<>=]/) }
end

.normalize_julia_constraint(constraint) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/dependabot/julia/requirement.rb', line 80

def self.normalize_julia_constraint(constraint)
  return normalize_caret_constraint(constraint) if constraint.match?(/^\^(\d+(?:\.\d+)*)/)
  return normalize_tilde_constraint(constraint) if constraint.match?(/^~(\d+(?:\.\d+)*)/)
  return normalize_range_constraint(constraint) if constraint.match?(HYPHEN_RANGE_PATTERN)

  # Julia treats plain version numbers as caret constraints (implicit ^)
  # e.g., "1.2.3" is equivalent to "^1.2.3" which means ">= 1.2.3, < 2.0.0"
  # See: https://pkgdocs.julialang.org/v1/compatibility/
  return normalize_caret_constraint("^#{constraint}") if constraint.match?(/^(\d+(?:\.\d+)*)$/)

  # Return as-is for standard gem requirements (>=, <=, ==, etc.)
  [constraint]
end

.normalize_version(version) ⇒ Object



69
70
71
72
73
# File 'lib/dependabot/julia/requirement.rb', line 69

def self.normalize_version(version)
  # Remove 'v' prefix if present (common in Julia)
  version = version.sub(/^v/, "") if version.match?(/^v\d/)
  version
end

.parse_compound_constraint(constraints) ⇒ Object



51
52
53
54
55
# File 'lib/dependabot/julia/requirement.rb', line 51

def self.parse_compound_constraint(constraints)
  # Handle compound constraints (e.g., ">= 1.0, < 2.0") as a single requirement
  normalized_constraints = constraints.flat_map { |c| normalize_julia_constraint(c) }
  [new(normalized_constraints)]
end

.parse_requirements(requirement_string) ⇒ Object



64
65
66
# File 'lib/dependabot/julia/requirement.rb', line 64

def self.parse_requirements(requirement_string)
  requirements_array(requirement_string)
end

.parse_separate_constraints(constraints) ⇒ Object



58
59
60
61
# File 'lib/dependabot/julia/requirement.rb', line 58

def self.parse_separate_constraints(constraints)
  # Handle separate version specs (e.g., "0.34, 0.35") as multiple requirements
  constraints.map { |constraint| new(normalize_julia_constraint(constraint)) }
end

.requirements_array(requirement_string) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/dependabot/julia/requirement.rb', line 11

def self.requirements_array(requirement_string)
  # Julia version specifiers can be:
  # - Exact: "1.2.3"
  # - Range: "1.2 - 1.3", ">=1.0, <2.0"
  # - Caret: "^1.2" (compatible within major version)
  # - Tilde: "~1.2.3" (compatible within minor version)
  # Note: Missing compat entry (nil/empty) means any version is acceptable
  return [new(">= 0")] if requirement_string.nil? || requirement_string.empty?

  constraints = requirement_string.split(",").map(&:strip)

  if compound_constraint?(constraints)
    parse_compound_constraint(constraints)
  else
    parse_separate_constraints(constraints)
  end
rescue Gem::Requirement::BadRequirementError
  [new(">= 0")]
end