rbs-patch 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a45288c145d3c26618712866de56ebeebabc1034e29960877834d5fc0518c58
4
- data.tar.gz: 12ae5d9f8fcd5721f05bac232b9dd10b8de6a23c47c73eba19801e65b3b33143
3
+ metadata.gz: be7a77838d494748f9f51925c7d26a78a70aee45fb143942b71630c79fa31518
4
+ data.tar.gz: 67ad4d7de09a5a558924e609894dc19c9e2266f7b3f311008e8300f7d41c43e8
5
5
  SHA512:
6
- metadata.gz: b939dc31112ba51d86e88c66b0000ac3abcf7baa9d5b57db79af3ee8003dc2cafbb90e2d6c4d08ffb53c3aebd8bb3c1d2b1bec7609911d03f7ed12105621a2b4
7
- data.tar.gz: '00998b3ec8d70b87cf45177244ed9dc0e8bdb572f95eb33e07f422a137a67922a6add9ddb06a00777de1eb00aa69eb787930b2e6b33704b890064e4c1b2683c5'
6
+ metadata.gz: 632f15418e48dca1b7a4f5202665a1b9248720ea267b6f49de41e3b8c3696ac3bc23f6010e22b3e9be8a551b593db3310be8ce6a93b27170590fd83c3b814637
7
+ data.tar.gz: 86fda57bc6b66c158b7c67c9b58dcc9f9bb4d13433204e0587e60338f8220aea9a52b386025ca48ab20eee27954b5779a2b54ab9382a05ad243918c5281610bf
data/README.md CHANGED
@@ -27,7 +27,160 @@ gem install rbs-patch
27
27
 
28
28
  ## Usage
29
29
 
30
- TODO: Write usage instructions here
30
+ ### Basic Usage
31
+
32
+ ```bash
33
+ # Apply patches to RBS files
34
+ rbs-patch base.rbs patch1.rbs patch2.rbs
35
+
36
+ # Mix files and directories
37
+ rbs-patch lib/types/ sig/patches/
38
+
39
+ # Output goes to stdout - redirect to save
40
+ rbs-patch base.rbs patch.rbs > output.rbs
41
+ ```
42
+
43
+ ### Programmatic Usage
44
+
45
+ ```ruby
46
+ require 'rbs/patch'
47
+
48
+ p = RBS::Patch.new
49
+
50
+ # Load from a single file
51
+ p.apply(path: Pathname("sig/user.rbs"))
52
+
53
+ # Load from a directory (all .rbs files)
54
+ p.apply(path: Pathname("sig/patches"))
55
+
56
+ # Apply from string
57
+ p.apply(<<~RBS)
58
+ class User
59
+ %a{patch:override}
60
+ def name: () -> String?
61
+ end
62
+ RBS
63
+
64
+ puts p.to_s
65
+ ```
66
+
67
+ ### Annotation Syntax
68
+
69
+ All patch operations use RBS annotations with the format `%a{patch:operation}` or `%a{patch:operation:target}`.
70
+
71
+ #### Method-level Operations
72
+
73
+ ##### `override` - Replace existing method signature
74
+
75
+ ```ruby
76
+ class User
77
+ %a{patch:override}
78
+ def name: () -> String? # Replaces existing method signature at the same position
79
+ end
80
+ ```
81
+
82
+ ##### `delete` - Remove method signature
83
+
84
+ ```ruby
85
+ class User
86
+ %a{patch:delete}
87
+ def email: () -> String # Removes this method from the class
88
+ end
89
+ ```
90
+
91
+ ##### `append_after(method_name)` - Insert method after specified method
92
+
93
+ ```ruby
94
+ class User
95
+ %a{patch:append_after(name)}
96
+ def nickname: () -> String? # Inserts after the 'name' method
97
+ end
98
+ ```
99
+
100
+ ##### `prepend_before(method_name)` - Insert method before specified method
101
+
102
+ ```ruby
103
+ class User
104
+ %a{patch:prepend_before(name)}
105
+ def id: () -> Integer # Inserts before the 'name' method
106
+ end
107
+ ```
108
+
109
+ #### Class/Module-level Operations
110
+
111
+ ##### `override` - Replace entire class/module
112
+
113
+ ```ruby
114
+ %a{patch:override}
115
+ class User
116
+ def name: () -> String # Completely replaces the User class definition
117
+ end
118
+ ```
119
+
120
+ ##### `delete` - Remove class/module
121
+
122
+ ```ruby
123
+ %a{patch:delete}
124
+ class User
125
+ end # Removes the entire User class
126
+ ```
127
+
128
+ ##### `append_after(ClassName)` - Insert class/module after specified class
129
+
130
+ ```ruby
131
+ %a{patch:append_after(User)}
132
+ class Admin
133
+ def permissions: () -> Array[String]
134
+ end # Inserts Admin class after User class
135
+ ```
136
+
137
+ ##### `prepend_before(ClassName)` - Insert class/module before specified class
138
+
139
+ ```ruby
140
+ %a{patch:prepend_before(User)}
141
+ class Guest
142
+ def readonly: () -> bool
143
+ end # Inserts Guest class before User class
144
+ ```
145
+
146
+ ### Working with Nested Modules
147
+
148
+ Operations work correctly within nested module structures:
149
+
150
+ ```ruby
151
+ module MyApp
152
+ module Models
153
+ %a{patch:append_after(User)}
154
+ class Admin
155
+ def role: () -> String
156
+ end
157
+ end
158
+ end
159
+ ```
160
+
161
+ ### Merging Multiple Definitions
162
+
163
+ Without annotations, multiple class definitions are merged:
164
+
165
+ ```ruby
166
+ p.apply(<<~RBS)
167
+ class User
168
+ def name: () -> String
169
+ end
170
+ RBS
171
+
172
+ p.apply(<<~RBS)
173
+ class User
174
+ def email: () -> String # Adds to existing User class
175
+ end
176
+ RBS
177
+
178
+ # Result:
179
+ # class User
180
+ # def name: () -> String
181
+ # def email: () -> String
182
+ # end
183
+ ```
31
184
 
32
185
  ## Development
33
186
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RBS
4
4
  class Patch
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.6"
6
6
  end
7
7
  end
data/lib/rbs/patch.rb CHANGED
@@ -8,8 +8,8 @@ module RBS
8
8
  class Patch # rubocop:disable Style/Documentation
9
9
  ANNOTATION_OVERRIDE = "patch:override"
10
10
  ANNOTATION_DELETE = "patch:delete"
11
- ANNOTATION_APPEND_AFTER = /\Apatch:append_after:(.*)\Z/
12
- ANNOTATION_PREPEND_BEFORE = /\Apatch:prepend_before:(.*)\Z/
11
+ ANNOTATION_APPEND_AFTER = /\Apatch:append_after\((.*)\)\Z/
12
+ ANNOTATION_PREPEND_BEFORE = /\Apatch:prepend_before\((.*)\)\Z/
13
13
 
14
14
  def initialize
15
15
  @decls = []
@@ -55,27 +55,72 @@ module RBS
55
55
 
56
56
  private
57
57
 
58
+ def extract_name(decl)
59
+ if decl.is_a?(::RBS::AST::Declarations::AliasDecl) # rubocop:disable Style/CaseLikeIf
60
+ decl.new_name.to_s
61
+ elsif decl.is_a?(::RBS::AST::Declarations::Base)
62
+ decl.name.to_s
63
+ elsif decl.is_a?(::RBS::AST::Members::LocationOnly)
64
+ ""
65
+ elsif decl.is_a?(::RBS::AST::Members::Alias) # rubocop:disable Lint/DuplicateBranch
66
+ decl.new_name.to_s
67
+ else # rubocop:disable Lint/DuplicateBranch
68
+ # ::RBS::AST::Members::t
69
+ decl.name.to_s
70
+ end
71
+ end
72
+
73
+ def extract_members(decl)
74
+ decl.members if decl.is_a?(::RBS::AST::Declarations::NestedDeclarationHelper)
75
+ end
76
+
77
+ def update(decl, location:)
78
+ if decl.respond_to?(:update)
79
+ # steep:ignore:start
80
+ decl.update(location:)
81
+ # steep:ignore:end
82
+ elsif decl.is_a?(AST::Declarations::Constant)
83
+ AST::Declarations::Constant.new(
84
+ name: decl.name, type: decl.type, location: location, comment: decl.comment, annotations: decl.annotations
85
+ )
86
+ elsif decl.is_a?(AST::Declarations::Global)
87
+ AST::Declarations::Global.new(
88
+ name: decl.name, type: decl.type, location: location, comment: decl.comment, annotations: decl.annotations
89
+ )
90
+ elsif decl.is_a?(AST::Declarations::TypeAlias)
91
+ AST::Declarations::TypeAlias.new(
92
+ name: decl.name, type_params: decl.type_params, type: decl.type, annotations: decl.annotations,
93
+ location: location, comment: decl.comment
94
+ )
95
+ elsif decl.is_a?(AST::Declarations::AliasDecl)
96
+ decl.class.new(
97
+ new_name: decl.new_name, old_name: decl.old_name, location: location, comment: decl.comment,
98
+ annotations: decl.annotations
99
+ )
100
+ elsif decl.is_a?(AST::Members::Mixin)
101
+ decl.class.new(
102
+ name: decl.name, args: decl.args, annotations: decl.annotations, location: location, comment: decl.comment
103
+ )
104
+ elsif decl.is_a?(AST::Members::Alias)
105
+ decl.class.new(
106
+ new_name: decl.new_name, old_name: decl.old_name, kind: decl.kind, annotations: decl.annotations,
107
+ location: location, comment: decl.comment
108
+ )
109
+ else
110
+ decl
111
+ end
112
+ end
113
+
58
114
  def walk(decls, name_stack = [], &block)
59
115
  decls.each do |decl|
60
- name = if decl.is_a?(::RBS::AST::Declarations::AliasDecl) # rubocop:disable Style/CaseLikeIf
61
- decl.new_name.to_s
62
- elsif decl.is_a?(::RBS::AST::Declarations::Base)
63
- decl.name.to_s
64
- elsif decl.is_a?(::RBS::AST::Members::LocationOnly)
65
- ""
66
- elsif decl.is_a?(::RBS::AST::Members::Alias) # rubocop:disable Lint/DuplicateBranch
67
- decl.new_name.to_s
68
- else # rubocop:disable Lint/DuplicateBranch
69
- # ::RBS::AST::Members::t
70
- decl.name.to_s
71
- end
72
- name_stack << name
116
+ name_stack << extract_name(decl)
73
117
  if decl.is_a?(::RBS::AST::Members::Base)
74
118
  yield decl, "#{name_stack[..-2]&.join("::")}##{name_stack[-1]}"
75
119
  else
76
120
  yield decl, name_stack.join("::")
77
121
  end
78
- walk(decl.members, name_stack, &block) if decl.is_a?(::RBS::AST::Declarations::NestedDeclarationHelper)
122
+ members = extract_members(decl)
123
+ walk(members, name_stack, &block) if members
79
124
  name_stack.pop
80
125
  end
81
126
  end
@@ -94,24 +139,46 @@ module RBS
94
139
  sep = decl.is_a?(::RBS::AST::Members::Base) ? "#" : "::"
95
140
  namespace, = to.rpartition(sep)
96
141
 
97
- target = namespace.empty? ? @decls : map[namespace]&.members # steep:ignore
142
+ target = namespace.empty? ? @decls : extract_members(map[namespace])
98
143
 
99
- # steep:ignore:start
100
- if target
144
+ unless target
145
+ @decls << decl # steep:ignore
146
+ return
147
+ end
148
+
149
+ if decl.is_a?(AST::Members::Var)
150
+ # AST::Members::Var does not support annotations.
151
+ index = target.rindex { |m| m.is_a?(decl.class) }
152
+ if index
153
+ decl = update(decl, location: target[index].location.dup)
154
+ target.insert(index + 1, decl)
155
+ else
156
+ index = target.find_index { |m| m.is_a?(AST::Members::MethodDefinition) }
157
+ if index
158
+ decl = update(decl, location: target[index].location.dup)
159
+ target.insert(index, decl)
160
+ else
161
+ target << decl
162
+ end
163
+ end
164
+ else
165
+ decl.annotations.delete_if { |a| process_annotations([a]) } # steep:ignore
101
166
  if after
102
- index = target.find_index { |m| m.name.to_s == after }
103
- target.insert(index + 1, decl) if index
167
+ index = target.find_index { |m| extract_name(m) == after }
168
+ return unless index
169
+
170
+ decl = update(decl, location: target[index].location.dup)
171
+ target.insert(index + 1, decl)
104
172
  elsif before
105
- index = target.find_index { |m| m.name.to_s == before }
106
- target.insert(index, decl) if index
173
+ index = target.find_index { |m| extract_name(m) == before }
174
+ return unless index
175
+
176
+ decl = update(decl, location: target[index].location.dup)
177
+ target.insert(index, decl)
107
178
  else
108
179
  target << decl
109
180
  end
110
- decl.annotations.delete_if { |a| process_annotations([a]) }
111
- else
112
- @decls << decl
113
181
  end
114
- # steep:ignore:end
115
182
  end
116
183
 
117
184
  def override(name, with:)
@@ -121,19 +188,18 @@ module RBS
121
188
  sep = with.is_a?(::RBS::AST::Members::Base) ? "#" : "::"
122
189
  namespace, _, name = name.rpartition(sep)
123
190
 
124
- # steep:ignore:start
125
191
  if namespace.empty?
126
192
  # top level decl
127
- index = @decls.find_index { |d| d.name.to_s == name }
128
- @decls[index] = with
193
+ index = @decls.find_index { |d| extract_name(d) == name }
194
+ @decls[index] = with # steep:ignore
129
195
  else
130
- index = map[namespace].members.find_index do |m|
131
- m.name.to_s == name
196
+ members = extract_members(map[namespace])
197
+ index = members.find_index do |m| # steep:ignore
198
+ extract_name(m) == name
132
199
  end
133
- map[namespace].members[index] = with
200
+ members[index] = with # steep:ignore
134
201
  end
135
- with.annotations.delete_if { |a| process_annotations([a]) }
136
- # steep:ignore:end
202
+ with.annotations.delete_if { |a| process_annotations([a]) } # steep:ignore
137
203
  end
138
204
 
139
205
  def delete(name)
@@ -143,14 +209,12 @@ module RBS
143
209
  sep = name.index("#") ? "#" : "::"
144
210
  namespace, _, name = name.rpartition(sep)
145
211
 
146
- # steep:ignore:start
147
212
  if namespace.empty?
148
213
  # top level decl
149
- @decls.delete_if { |d| d.name.to_s == name }
214
+ @decls.delete_if { |d| extract_name(d) == name }
150
215
  else
151
- map[namespace].members.delete_if { |m| m.name.to_s == name }
216
+ extract_members(map[namespace])&.delete_if { |m| extract_name(m) == name }
152
217
  end
153
- # steep:ignore:end
154
218
  end
155
219
 
156
220
  def process_annotations(annotations) # steep:ignore
data/sig/rbs/patch.rbs CHANGED
@@ -22,7 +22,13 @@ module RBS
22
22
 
23
23
  private
24
24
 
25
- def walk: (Array[t] decls, ?Array[String] name_stack) { (::RBS::AST::Declarations::t | ::RBS::AST::Members::t, String) -> void } -> void
25
+ def extract_name: (t decl) -> String
26
+
27
+ def extract_members: (t decl) -> (Array[::RBS::AST::Declarations::Class::member] | Array[::RBS::AST::Declarations::Module::member] | nil)
28
+
29
+ def update: (t decl, location: untyped) -> t
30
+
31
+ def walk: (Array[t] decls, ?Array[String] name_stack) { (t, String) -> void } -> void
26
32
 
27
33
  def decl_map: () -> Hash[String, t]
28
34
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbs-patch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koji NAKAMURA