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 +4 -4
- data/README.md +154 -1
- data/lib/rbs/patch/version.rb +1 -1
- data/lib/rbs/patch.rb +103 -39
- data/sig/rbs/patch.rbs +7 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be7a77838d494748f9f51925c7d26a78a70aee45fb143942b71630c79fa31518
|
|
4
|
+
data.tar.gz: 67ad4d7de09a5a558924e609894dc19c9e2266f7b3f311008e8300f7d41c43e8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
|
data/lib/rbs/patch/version.rb
CHANGED
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
|
|
12
|
-
ANNOTATION_PREPEND_BEFORE = /\Apatch:prepend_before
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
142
|
+
target = namespace.empty? ? @decls : extract_members(map[namespace])
|
|
98
143
|
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
103
|
-
|
|
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
|
|
106
|
-
|
|
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
|
|
128
|
-
@decls[index] = with
|
|
193
|
+
index = @decls.find_index { |d| extract_name(d) == name }
|
|
194
|
+
@decls[index] = with # steep:ignore
|
|
129
195
|
else
|
|
130
|
-
|
|
131
|
-
|
|
196
|
+
members = extract_members(map[namespace])
|
|
197
|
+
index = members.find_index do |m| # steep:ignore
|
|
198
|
+
extract_name(m) == name
|
|
132
199
|
end
|
|
133
|
-
|
|
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
|
|
214
|
+
@decls.delete_if { |d| extract_name(d) == name }
|
|
150
215
|
else
|
|
151
|
-
map[namespace]
|
|
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
|
|
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
|
|