Class: RBS::Patch

Inherits:
Object
  • Object
show all
Defined in:
lib/rbs/patch.rb,
lib/rbs/patch/version.rb,
sig/rbs/patch.rbs,
sig/generated/rbs/patch.rbs

Overview

rubocop:disable Style/Documentation

Constant Summary collapse

ANNOTATION_OVERRIDE =

@rbs! type t = ::RBS::AST::Declarations::t | ::RBS::AST::Members::t

Returns:

  • (::String)
"patch:override"
ANNOTATION_DELETE =

Returns:

  • (::String)
"patch:delete"
ANNOTATION_APPEND_AFTER =

Returns:

  • (::Regexp)
/\Apatch:append_after\((.*)\)\Z/
ANNOTATION_PREPEND_BEFORE =

Returns:

  • (::Regexp)
/\Apatch:prepend_before\((.*)\)\Z/
VERSION =

Returns:

  • (String)
"0.1.7"

Instance Method Summary collapse

Constructor Details

#initializePatch

: -> void



19
20
21
# File 'lib/rbs/patch.rb', line 19

def initialize
  @decls = []
end

Instance Method Details

#add(decl, to:, after: nil, before: nil) ⇒ void

This method returns an undefined value.

: (t decl, to: String, ?after: String?, ?before: String?) -> void

Parameters:

  • decl (t)
  • to: (String)
  • after: (String, nil) (defaults to: nil)
  • before: (String, nil) (defaults to: nil)


149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/rbs/patch.rb', line 149

def add(decl, to:, after: nil, before: nil)
  map = decl_map
  return if map.key?(to)

  sep = decl.is_a?(::RBS::AST::Members::Base) ? "#" : "::"
  namespace, = to.rpartition(sep)

  target = namespace.empty? ? @decls : extract_members(map[namespace])

  unless target
    @decls << decl if decl.is_a?(AST::Declarations::Base)
    return
  end

  if decl.is_a?(AST::Members::Var)
    # AST::Members::Var does not support annotations.
    index = target.rindex { |m| m.is_a?(decl.class) }
    if index
      decl = update(decl, location: target[index].location.dup)
      target.insert(index + 1, decl)
    else
      index = target.find_index { |m| m.is_a?(AST::Members::MethodDefinition) }
      if index
        decl = update(decl, location: target[index].location.dup)
        target.insert(index, decl)
      else
        target << decl
      end
    end
  else
    decl.annotations.delete_if { |a| process_annotations([a]) } # steep:ignore
    if after
      index = target.find_index { |m| extract_name(m) == after }
      return unless index

      decl = update(decl, location: target[index].location.dup)
      target.insert(index + 1, decl)
    elsif before
      index = target.find_index { |m| extract_name(m) == before }
      return unless index

      decl = update(decl, location: target[index].location.dup)
      target.insert(index, decl)
    else
      target << decl
    end
  end
end

#apply(source = nil, path: nil) ⇒ void

This method returns an undefined value.

: (?untyped? String, ?path: Pathname?) -> void

Parameters:

  • (untyped? String)
  • path: (Pathname, nil) (defaults to: nil)


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/rbs/patch.rb', line 32

def apply(source = nil, path: nil)
  unless path.nil?
    files = Set[]
    ::RBS::FileFinder.each_file(path, skip_hidden: true) do |path|
      next if files.include?(path)

      files << path
      apply ::RBS::Buffer.new(name: path, content: path.read(encoding: "UTF-8"))
    end
    return
  end

  _, _, decls = ::RBS::Parser.parse_signature(source)
  walk(decls) do |decl, name|
    ope, arg = process_annotations(decl.annotations) if decl.respond_to?(:annotations) # steep:ignore

    case ope
    when :override
      override(name, with: decl)
    when :delete
      delete(name)
    when :append_after
      add(decl, to: name, after: arg)
    when :prepend_before
      add(decl, to: name, before: arg)
    else
      add(decl, to: name)
    end
  end
end

#decl_mapHash[String, t]

: () -> Hash[String, t]

Returns:

  • (Hash[String, t])


141
142
143
144
145
146
# File 'lib/rbs/patch.rb', line 141

def decl_map
  # @type var map: Hash[String, ::RBS::Patch::t]
  map = {}
  walk(@decls) { |decl, name| map[name] = decl }
  map
end

#delete(name) ⇒ void

This method returns an undefined value.

: (String name) -> void

Parameters:

  • name (String)


226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/rbs/patch.rb', line 226

def delete(name)
  map = decl_map
  return unless map.key?(name)

  sep = name.index("#") ? "#" : "::"
  namespace, _, name = name.rpartition(sep)

  if namespace.empty?
    # top level decl
    @decls.delete_if { |d| extract_name(d) == name }
  else
    extract_members(map[namespace])&.delete_if { |m| extract_name(m) == name }
  end
end

#extract_members(decl) ⇒ Array[AST::Declarations::Class::member], ...

: (t decl) -> (Array | Array | nil)

Parameters:

  • decl (t)

Returns:

  • (Array[AST::Declarations::Class::member], Array[AST::Declarations::Module::member], nil)


82
83
84
# File 'lib/rbs/patch.rb', line 82

def extract_members(decl)
  decl.members if decl.is_a?(::RBS::AST::Declarations::NestedDeclarationHelper)
end

#extract_name(decl) ⇒ String

: (t decl) -> String

Parameters:

  • decl (t)

Returns:

  • (String)


66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/rbs/patch.rb', line 66

def extract_name(decl)
  if decl.is_a?(::RBS::AST::Declarations::AliasDecl) # rubocop:disable Style/CaseLikeIf
    decl.new_name.to_s
  elsif decl.is_a?(::RBS::AST::Declarations::Base)
    decl.name.to_s
  elsif decl.is_a?(::RBS::AST::Members::LocationOnly)
    ""
  elsif decl.is_a?(::RBS::AST::Members::Alias) # rubocop:disable Lint/DuplicateBranch
    decl.new_name.to_s
  else # rubocop:disable Lint/DuplicateBranch
    # ::RBS::AST::Members::t
    decl.name.to_s
  end
end

#override(name, with:) ⇒ void

This method returns an undefined value.

: (String name, with: t) -> void

Parameters:

  • name (String)
  • with: (t)


199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/rbs/patch.rb', line 199

def override(name, with:)
  map = decl_map
  return unless map.key?(name)

  old = map[name]
  # No way to explicitly clear a comment via override: an override without a
  # comment always keeps the overridden one. Revisit if that's ever needed.
  with = update(with, comment: old.comment) if with.comment.nil? && old.comment # steep:ignore

  sep = with.is_a?(::RBS::AST::Members::Base) ? "#" : "::"
  namespace, _, name = name.rpartition(sep)

  if namespace.empty?
    # top level decl
    index = @decls.find_index { |d| extract_name(d) == name }
    @decls[index] = with # steep:ignore
  else
    members = extract_members(map[namespace])
    index = members.find_index do |m| # steep:ignore
      extract_name(m) == name
    end
    members[index] = with # steep:ignore
  end
  with.annotations.delete_if { |a| process_annotations([a]) } # steep:ignore
end

#process_annotations(annotations) ⇒ [ :override, nil ], ...

Parameters:

  • annotations (Array[AST::Annotation])

Returns:

  • ([ :override, nil ], [ :delte, nil ], [ :append_after, String ], [ :prepend_before, String ], nil)


246
247
248
249
250
251
252
253
254
255
256
# File 'lib/rbs/patch.rb', line 246

def process_annotations(annotations) # steep:ignore
  if annotations.any? { |a| a.string == ANNOTATION_OVERRIDE }
    [:override, nil]
  elsif annotations.any? { |a| a.string == ANNOTATION_DELETE }
    [:delete, nil]
  elsif (anno = annotations.find { |a| a.string.match(ANNOTATION_APPEND_AFTER) })
    [:append_after, anno.string.match(ANNOTATION_APPEND_AFTER)&.[](1) || ""]
  elsif (anno = annotations.find { |a| a.string.match(ANNOTATION_PREPEND_BEFORE) })
    [:prepend_before, anno.string.match(ANNOTATION_PREPEND_BEFORE)&.[](1) || ""]
  end
end

#to_sString

: -> String

Returns:

  • (String)


24
25
26
27
28
29
# File 'lib/rbs/patch.rb', line 24

def to_s
  io = ::StringIO.new
  ::RBS::Writer.new(out: io).write(@decls)
  io.rewind
  io.read || ""
end

#update(decl, location: decl.location, comment: nil) ⇒ t

: (t decl, ?location: untyped, ?comment: untyped) -> t

Parameters:

  • decl (t)
  • location: (Object) (defaults to: decl.location)
  • comment: (Object) (defaults to: nil)

Returns:

  • (t)


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/rbs/patch.rb', line 87

def update(decl, location: decl.location, comment: nil)
  if decl.is_a?(AST::Declarations::Constant) # rubocop:disable Style/CaseLikeIf
    AST::Declarations::Constant.new(
      name: decl.name, type: decl.type, location: location, comment: comment || decl.comment,
      annotations: decl.annotations
    )
  elsif decl.is_a?(AST::Declarations::Global)
    AST::Declarations::Global.new(
      name: decl.name, type: decl.type, location: location, comment: comment || decl.comment,
      annotations: decl.annotations
    )
  elsif decl.is_a?(AST::Declarations::TypeAlias)
    AST::Declarations::TypeAlias.new(
      name: decl.name, type_params: decl.type_params, type: decl.type, annotations: decl.annotations,
      location: location, comment: comment || decl.comment
    )
  elsif decl.is_a?(AST::Declarations::AliasDecl)
    decl.class.new(
      new_name: decl.new_name, old_name: decl.old_name, location: location, comment: comment || decl.comment,
      annotations: decl.annotations
    )
  elsif decl.is_a?(AST::Members::Mixin)
    decl.class.new(
      name: decl.name, args: decl.args, annotations: decl.annotations, location: location,
      comment: comment || decl.comment
    )
  elsif decl.is_a?(AST::Members::Alias)
    decl.class.new(
      new_name: decl.new_name, old_name: decl.old_name, kind: decl.kind, annotations: decl.annotations,
      location: location, comment: comment || decl.comment
    )
  elsif decl.is_a?(AST::Members::Var) || decl.is_a?(AST::Members::LocationOnly)
    decl
  else
    decl.update(location: location, comment: comment || decl.comment)
  end
end

#walk(decls, name_stack = []) {|arg0, arg1| ... } ⇒ void

This method returns an undefined value.

: (Array decls, ?Array name_stack) { (t, String) -> void } -> void

Parameters:

  • decls (Array[t])
  • name_stack (Array[String]) (defaults to: [])

Yields:

Yield Parameters:

  • arg0 (t)
  • arg1 (String)

Yield Returns:

  • (void)


126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/rbs/patch.rb', line 126

def walk(decls, name_stack = [], &block)
  decls.each do |decl|
    name_stack << extract_name(decl)
    if decl.is_a?(::RBS::AST::Members::Base)
      yield decl, "#{name_stack[..-2]&.join("::")}##{name_stack[-1]}"
    else
      yield decl, name_stack.join("::")
    end
    members = extract_members(decl)
    walk(members, name_stack, &block) if members
    name_stack.pop
  end
end