Module: Ryac::Pipeline::SourcePatcher

Included in:
Minifier, Stage, StageRunner
Defined in:
lib/ryac/pipeline/source_patcher.rb,
sig/ryac/pipeline/source_patcher.rbs

Instance Method Summary collapse

Instance Method Details

#apply_patches(source, patches) ⇒ String

The one patch applier: every stage splices byte patches through here. Patches must be disjoint — an overlap means two rewrites fought over the same bytes, and applying either order silently corrupts the output, so it raises with both patches named. The end-position tie-break keeps same-start insertions deterministic.

Parameters:

  • source (String)
  • patches (Array[{ start: Integer, end: Integer, replacement: String }])

Returns:

  • (String)


17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/ryac/pipeline/source_patcher.rb', line 17

def apply_patches(source, patches)
  return source if patches.none?
  sorted = patches.sort_by { |p| [-p[:start], -p[:end]] }
  sorted.each_cons(2) do |after, before|
    # each_cons(2) yields only full pairs; the destructuring alone is
    # what makes the elements look nilable.
    # @type var after: { start: Integer, end: Integer, replacement: String }
    # @type var before: { start: Integer, end: Integer, replacement: String }
    if before[:end] > after[:start]
      raise MinifyError, "overlapping patches: #{before.inspect} / #{after.inspect}"
    end
  end
  result = source.b.dup
  sorted.each do |patch|
    result[patch[:start]...patch[:end]] = patch[:replacement].b
  end
  result.force_encoding(source.encoding)
end

#mk(node, replacement) ⇒ { start: Integer, end: Integer, replacement: String }

Parameters:

  • node (Prism::Node)
  • replacement (String)

Returns:

  • ({ start: Integer, end: Integer, replacement: String })


8
9
10
# File 'lib/ryac/pipeline/source_patcher.rb', line 8

def mk(node, replacement)
  { start: node.location.start_offset, end: node.location.end_offset, replacement: replacement }
end

#src(source, node) ⇒ String

byteslice is typed String? but node offsets always lie inside the source the node was parsed from, so the result is never nil.

Parameters:

  • source (String)
  • node (Prism::Node)

Returns:

  • (String)


38
39
40
# File 'lib/ryac/pipeline/source_patcher.rb', line 38

def src(source, node) # steep:ignore MethodBodyTypeMismatch
  source.byteslice(node.location.start_offset, node.location.length)
end

#verify_parses(text, label) ⇒ void

This method returns an undefined value.

The output of every pipeline permutation must at minimum be valid Ruby. Prism also rejects duplicated parameter names, so a rename that collides inside a signature is caught here, at the stage that produced it.

Parameters:

  • text (String)
  • label (String)

Raises:



46
47
48
49
50
51
# File 'lib/ryac/pipeline/source_patcher.rb', line 46

def verify_parses(text, label)
  return if text.empty?

  errors = Prism.parse(text).errors
  raise InvalidOutputError.new(label, errors) unless errors.empty?
end