rbs_macros 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +49 -0
- data/.rubocop_new.yml +269 -0
- data/CHANGELOG.md +6 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +123 -0
- data/Rakefile +41 -0
- data/Steepfile +22 -0
- data/lib/rbs_macros/config.rb +19 -0
- data/lib/rbs_macros/environment.rb +50 -0
- data/lib/rbs_macros/exec_ctx.rb +66 -0
- data/lib/rbs_macros/library_registry.rb +52 -0
- data/lib/rbs_macros/macro.rb +12 -0
- data/lib/rbs_macros/macros/forwardable.rb +197 -0
- data/lib/rbs_macros/meta_module.rb +65 -0
- data/lib/rbs_macros/project.rb +110 -0
- data/lib/rbs_macros/version.rb +5 -0
- data/lib/rbs_macros.rb +137 -0
- data/rbs_collection.lock.yaml +60 -0
- data/rbs_collection.yaml +24 -0
- data/sig/generated/rbs_macros/library_registry.rbs +5 -0
- data/sig/rbs_macros/config.rbs +13 -0
- data/sig/rbs_macros/environment.rbs +42 -0
- data/sig/rbs_macros/exec_ctx.rbs +14 -0
- data/sig/rbs_macros/library_registry.rbs +16 -0
- data/sig/rbs_macros/macro.rbs +5 -0
- data/sig/rbs_macros/macros/forwardable.rbs +29 -0
- data/sig/rbs_macros/meta_module.rbs +29 -0
- data/sig/rbs_macros/project.rbs +22 -0
- data/sig/rbs_macros/version.rbs +3 -0
- data/sig/rbs_macros.rbs +7 -0
- data/sig-private/test/environment_test.rbs +3 -0
- data/sig-private/test/library_registry_test.rbs +11 -0
- data/sig-private/test/macros/forwardable_test.rbs +7 -0
- data/sig-private/test/rbs_macros_test.rbs +13 -0
- metadata +147 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "prism"
|
4
|
+
|
5
|
+
module RbsMacros
|
6
|
+
# An environment for the Ruby program being analyzed.
|
7
|
+
class Environment
|
8
|
+
attr_reader :rbs, :object_class, :decls
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@rbs = RBS::Environment.new
|
12
|
+
@object_class = MetaClass.new(self, "Object", is_class: true)
|
13
|
+
@decls = []
|
14
|
+
@exact_handlers = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_handler(name, handler)
|
18
|
+
(@exact_handlers[name.to_sym] ||= []) << handler
|
19
|
+
end
|
20
|
+
|
21
|
+
def invoke(params)
|
22
|
+
handlers = @exact_handlers[params.name]
|
23
|
+
handlers&.each do |handler|
|
24
|
+
handler.(params)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def meta_eval_ruby(code, filename:)
|
29
|
+
result = Prism.parse(code, filepath: "#{filename}.rb")
|
30
|
+
raise ArgumentError, "Parse error: #{result.errors}" if result.failure?
|
31
|
+
|
32
|
+
ExecCtx.new(
|
33
|
+
env: self,
|
34
|
+
filename:,
|
35
|
+
self: nil, # TODO
|
36
|
+
cref: @object_class,
|
37
|
+
cref_dynamic: @object_class,
|
38
|
+
locals: {}
|
39
|
+
).eval_node(result.value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_decl(decl, mod:, file:)
|
43
|
+
@decls << DeclarationEntry.new(declaration: decl, mod:, file:)
|
44
|
+
end
|
45
|
+
|
46
|
+
HandlerParams = _ = Data.define(:env, :filename, :receiver, :name, :positional, :keyword, :block) # rubocop:disable Naming/ConstantName
|
47
|
+
|
48
|
+
DeclarationEntry = _ = Data.define(:declaration, :mod, :file) # rubocop:disable Naming/ConstantName
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsMacros
|
4
|
+
ExecCtx = _ = Data.define(:env, :filename, :self, :cref, :cref_dynamic, :locals) # rubocop:disable Naming/ConstantName
|
5
|
+
|
6
|
+
# Context, including self, class, and local variables
|
7
|
+
class ExecCtx
|
8
|
+
def eval_node(node)
|
9
|
+
case node
|
10
|
+
when nil
|
11
|
+
# do nothing
|
12
|
+
when Prism::CallNode
|
13
|
+
recv = \
|
14
|
+
if node.receiver
|
15
|
+
eval_node(node.receiver)
|
16
|
+
else
|
17
|
+
self.self
|
18
|
+
end
|
19
|
+
positional = [] # : Array[Object]
|
20
|
+
keyword = {} # : Hash[Object, Object]
|
21
|
+
node.arguments&.arguments&.each do |arg|
|
22
|
+
positional << eval_node(arg)
|
23
|
+
end
|
24
|
+
env.invoke(
|
25
|
+
Environment::HandlerParams.new(
|
26
|
+
env:,
|
27
|
+
filename:,
|
28
|
+
receiver: recv,
|
29
|
+
name: node.name,
|
30
|
+
positional:,
|
31
|
+
keyword:,
|
32
|
+
block: nil
|
33
|
+
)
|
34
|
+
)
|
35
|
+
when Prism::ClassNode
|
36
|
+
klass = cref.define_module(node.name)
|
37
|
+
klass.class!
|
38
|
+
with(
|
39
|
+
self: klass,
|
40
|
+
cref: klass,
|
41
|
+
cref_dynamic: klass,
|
42
|
+
locals: {}
|
43
|
+
).eval_node(node.body)
|
44
|
+
when Prism::ModuleNode
|
45
|
+
mod = cref.define_module(node.name)
|
46
|
+
mod.module!
|
47
|
+
with(
|
48
|
+
self: mod,
|
49
|
+
cref: mod,
|
50
|
+
cref_dynamic: mod,
|
51
|
+
locals: {}
|
52
|
+
).eval_node(node.body)
|
53
|
+
when Prism::ProgramNode
|
54
|
+
eval_node(node.statements)
|
55
|
+
when Prism::StatementsNode
|
56
|
+
node.body.each { |stmt| eval_node(stmt) }
|
57
|
+
when Prism::StringNode
|
58
|
+
node.unescaped.dup.freeze
|
59
|
+
when Prism::SymbolNode
|
60
|
+
node.unescaped.to_sym
|
61
|
+
else
|
62
|
+
$stderr.puts "Dismissing node: #{node.inspect}" # rubocop:disable Style/StderrPuts
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module RbsMacros
|
6
|
+
# RbsMacros allow you to define a reusable set of macro definitions
|
7
|
+
# called a library.
|
8
|
+
# This is usually registered to the global singleton of LibraryRegistry
|
9
|
+
# like:
|
10
|
+
#
|
11
|
+
# # my_library/rbs_macros.rb
|
12
|
+
# RbsMacros::LibraryRegistry.register_macros("my_library/rbs_macros") do |macros|
|
13
|
+
# macros << MyMacro1
|
14
|
+
# macros << MyMacro2
|
15
|
+
# end
|
16
|
+
class LibraryRegistry
|
17
|
+
extend SingleForwardable
|
18
|
+
def_single_delegators :@global, :register_macros
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@libraries = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def register_macros(name, macros = [], &block)
|
25
|
+
a = @libraries.fetch(name) { |k| @libraries[k] = [] }
|
26
|
+
a.push(*macros)
|
27
|
+
block&.(a)
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def lookup_macros(name, soft_fail: false)
|
32
|
+
unless @libraries.key?(name)
|
33
|
+
require_library(name, soft_fail:)
|
34
|
+
raise ArgumentError, "Unknown library: #{name}" if !@libraries.key?(name) && !soft_fail
|
35
|
+
end
|
36
|
+
|
37
|
+
@libraries[name] || []
|
38
|
+
end
|
39
|
+
|
40
|
+
def require_library(name, soft_fail: false)
|
41
|
+
# To be implemented by subclasses
|
42
|
+
raise ArgumentError, "Unknown library: #{name}" unless soft_fail
|
43
|
+
end
|
44
|
+
|
45
|
+
@global = LibraryRegistry.new
|
46
|
+
def @global.require_library(name, soft_fail: false)
|
47
|
+
require name
|
48
|
+
rescue LoadError
|
49
|
+
raise unless soft_fail
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsMacros
|
4
|
+
# Base class for macro implementations.
|
5
|
+
# Macros react to method invocations in Ruby code (usually in module/class bodies)
|
6
|
+
# and generate RBS declarations for them.
|
7
|
+
class Macro
|
8
|
+
def setup(env) # rubocop:disable Lint/UnusedMethodArgument
|
9
|
+
raise NoMethodError, "Not implemented: #{self.class}#setup"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rbs_macros"
|
4
|
+
|
5
|
+
module RbsMacros
|
6
|
+
module Macros
|
7
|
+
# TODO: resolve duplication between ForwardableMacros and SingleForwardableMacros
|
8
|
+
# TODO: fallback to untyped even when something is wrong
|
9
|
+
|
10
|
+
# Implements macros for the `Forwardable` module.
|
11
|
+
class ForwardableMacros < Macro
|
12
|
+
def setup(env)
|
13
|
+
env.register_handler(:def_delegator, method(:meta_def_delegator))
|
14
|
+
env.register_handler(:def_instance_delegator, method(:meta_def_delegator))
|
15
|
+
|
16
|
+
env.register_handler(:def_delegators, method(:meta_def_delegators))
|
17
|
+
env.register_handler(:def_instance_delegators, method(:meta_def_delegators))
|
18
|
+
end
|
19
|
+
|
20
|
+
def meta_def_delegator(params)
|
21
|
+
recv = params.receiver
|
22
|
+
return unless recv.is_a?(MetaModule)
|
23
|
+
|
24
|
+
accessor = params.positional[0]
|
25
|
+
method = params.positional[1]
|
26
|
+
ali = params.positional[2] || method
|
27
|
+
return unless accessor.is_a?(Symbol) || accessor.is_a?(String)
|
28
|
+
return unless method.is_a?(Symbol) || method.is_a?(String)
|
29
|
+
return unless ali.is_a?(Symbol) || ali.is_a?(String)
|
30
|
+
|
31
|
+
builder = RBS::DefinitionBuilder.new(env: params.env.rbs)
|
32
|
+
self_instance = builder.build_instance(recv.rbs_type)
|
33
|
+
accessor_type =
|
34
|
+
case accessor.to_s
|
35
|
+
when /\A@/
|
36
|
+
ivar = self_instance.instance_variables[accessor.to_sym]
|
37
|
+
return unless ivar
|
38
|
+
|
39
|
+
ivar.type
|
40
|
+
else
|
41
|
+
# TODO
|
42
|
+
return
|
43
|
+
end
|
44
|
+
accessor_instance = widened_instance(accessor_type, builder:)
|
45
|
+
return unless accessor_instance
|
46
|
+
|
47
|
+
meth = accessor_instance[0].methods[method.to_sym]&.sub(accessor_instance[1])
|
48
|
+
return unless meth
|
49
|
+
|
50
|
+
params.env.add_decl(
|
51
|
+
RBS::AST::Members::MethodDefinition.new(
|
52
|
+
name: ali.to_sym,
|
53
|
+
kind: :instance,
|
54
|
+
overloads:
|
55
|
+
meth.defs.map do |typedef|
|
56
|
+
RBS::AST::Members::MethodDefinition::Overload.new(
|
57
|
+
method_type: typedef.type,
|
58
|
+
annotations: []
|
59
|
+
)
|
60
|
+
end,
|
61
|
+
annotations: [],
|
62
|
+
location: nil,
|
63
|
+
comment: nil,
|
64
|
+
overloading: false,
|
65
|
+
visibility: nil
|
66
|
+
),
|
67
|
+
mod: recv,
|
68
|
+
file: params.filename
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def meta_def_delegators(params)
|
73
|
+
recv = params.receiver
|
74
|
+
return unless recv.is_a?(MetaModule)
|
75
|
+
|
76
|
+
accessor = params.positional[0]
|
77
|
+
methods = params.positional[1..] || []
|
78
|
+
methods.each do |method|
|
79
|
+
params.env.invoke(
|
80
|
+
params.with(
|
81
|
+
name: :def_instance_delegator,
|
82
|
+
positional: [accessor, method],
|
83
|
+
keyword: {},
|
84
|
+
block: nil
|
85
|
+
)
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def widened_instance(type, builder:)
|
93
|
+
case type
|
94
|
+
when RBS::Types::ClassInstance
|
95
|
+
# Using public_send because tapp_subst is declared as private although defined as public.
|
96
|
+
[builder.build_instance(type.name), builder.public_send(:tapp_subst, type.name, type.args)]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Implements macros for the `SingleForwardable` module.
|
102
|
+
class SingleForwardableMacros < Macro
|
103
|
+
def setup(env)
|
104
|
+
env.register_handler(:def_delegator, method(:meta_def_delegator))
|
105
|
+
env.register_handler(:def_single_delegator, method(:meta_def_delegator))
|
106
|
+
|
107
|
+
env.register_handler(:def_delegators, method(:meta_def_delegators))
|
108
|
+
env.register_handler(:def_single_delegators, method(:meta_def_delegators))
|
109
|
+
end
|
110
|
+
|
111
|
+
def meta_def_delegator(params)
|
112
|
+
recv = params.receiver
|
113
|
+
return unless recv.is_a?(MetaModule)
|
114
|
+
|
115
|
+
accessor = params.positional[0]
|
116
|
+
method = params.positional[1]
|
117
|
+
ali = params.positional[2] || method
|
118
|
+
return unless accessor.is_a?(Symbol) || accessor.is_a?(String)
|
119
|
+
return unless method.is_a?(Symbol) || method.is_a?(String)
|
120
|
+
return unless ali.is_a?(Symbol) || ali.is_a?(String)
|
121
|
+
|
122
|
+
builder = RBS::DefinitionBuilder.new(env: params.env.rbs)
|
123
|
+
singleton = builder.build_singleton(recv.rbs_type)
|
124
|
+
accessor_type =
|
125
|
+
case accessor.to_s
|
126
|
+
when /\A@/
|
127
|
+
ivar = singleton.instance_variables[accessor.to_sym]
|
128
|
+
return unless ivar
|
129
|
+
|
130
|
+
ivar.type
|
131
|
+
else
|
132
|
+
# TODO
|
133
|
+
return
|
134
|
+
end
|
135
|
+
accessor_instance = widened_instance(accessor_type, builder:)
|
136
|
+
return unless accessor_instance
|
137
|
+
|
138
|
+
meth = accessor_instance[0].methods[method.to_sym]&.sub(accessor_instance[1])
|
139
|
+
return unless meth
|
140
|
+
|
141
|
+
params.env.add_decl(
|
142
|
+
RBS::AST::Members::MethodDefinition.new(
|
143
|
+
name: ali.to_sym,
|
144
|
+
kind: :singleton,
|
145
|
+
overloads:
|
146
|
+
meth.defs.map do |typedef|
|
147
|
+
RBS::AST::Members::MethodDefinition::Overload.new(
|
148
|
+
method_type: typedef.type,
|
149
|
+
annotations: []
|
150
|
+
)
|
151
|
+
end,
|
152
|
+
annotations: [],
|
153
|
+
location: nil,
|
154
|
+
comment: nil,
|
155
|
+
overloading: false,
|
156
|
+
visibility: nil
|
157
|
+
),
|
158
|
+
mod: recv,
|
159
|
+
file: params.filename
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
def meta_def_delegators(params)
|
164
|
+
recv = params.receiver
|
165
|
+
return unless recv.is_a?(MetaModule)
|
166
|
+
|
167
|
+
accessor = params.positional[0]
|
168
|
+
methods = params.positional[1..] || []
|
169
|
+
methods.each do |method|
|
170
|
+
params.env.invoke(
|
171
|
+
params.with(
|
172
|
+
name: :def_single_delegator,
|
173
|
+
positional: [accessor, method],
|
174
|
+
keyword: {},
|
175
|
+
block: nil
|
176
|
+
)
|
177
|
+
)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def widened_instance(type, builder:)
|
184
|
+
case type
|
185
|
+
when RBS::Types::ClassInstance
|
186
|
+
# Using public_send because tapp_subst is declared as private although defined as public.
|
187
|
+
[builder.build_instance(type.name), builder.public_send(:tapp_subst, type.name, type.args)]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
RbsMacros::LibraryRegistry.register_macros("rbs_macros/macros/forwardable") do |macros|
|
195
|
+
macros << RbsMacros::Macros::ForwardableMacros
|
196
|
+
macros << RbsMacros::Macros::SingleForwardableMacros
|
197
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsMacros
|
4
|
+
# Designates a module in a Ruby program being analyzed.
|
5
|
+
class MetaModule
|
6
|
+
attr_reader :env, :name, :is_class, :superclass
|
7
|
+
|
8
|
+
def initialize(env, name, is_class: nil, superclass: nil)
|
9
|
+
@env = env
|
10
|
+
@name = name
|
11
|
+
@is_class = is_class
|
12
|
+
@superclass = superclass
|
13
|
+
@constants = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def rbs_type
|
17
|
+
return @rbs_type if defined?(@rbs_type)
|
18
|
+
|
19
|
+
*ns, name = (@name || raise("Anonymous module given")).split("::")
|
20
|
+
@rbs_type = RBS::TypeName.new(
|
21
|
+
name: (name || raise("Anonymous module gien")).to_sym,
|
22
|
+
namespace: RBS::Namespace.new(
|
23
|
+
path: ns.map(&:to_sym),
|
24
|
+
absolute: true
|
25
|
+
)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def class!
|
30
|
+
@is_class = true if @is_class.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def module!
|
34
|
+
@is_class = false if @is_class.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def define_module(name)
|
38
|
+
@constants[name] ||= MetaModule.new(@env, child_module_name(name.to_s))
|
39
|
+
end
|
40
|
+
|
41
|
+
def meta_const_set(name, value)
|
42
|
+
@constants[name] = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def meta_const_get(name)
|
46
|
+
@constants[name]
|
47
|
+
end
|
48
|
+
|
49
|
+
def meta_constants
|
50
|
+
@constants.keys
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def child_module_name(child_name)
|
56
|
+
if child_name && (name && name != "Object")
|
57
|
+
"#{name}::#{child_name}"
|
58
|
+
elsif child_name
|
59
|
+
child_name
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
MetaClass = MetaModule
|
65
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "pathname"
|
5
|
+
|
6
|
+
module RbsMacros
|
7
|
+
# Refers to the filesystem RbsMacros operates on.
|
8
|
+
class AbstractProject
|
9
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
10
|
+
def glob(
|
11
|
+
ext:,
|
12
|
+
include:,
|
13
|
+
exclude:,
|
14
|
+
&block
|
15
|
+
)
|
16
|
+
raise NoMethodError, "Not implemented: #{self.class}#each_rbs"
|
17
|
+
end
|
18
|
+
|
19
|
+
def write(path, content)
|
20
|
+
raise NoMethodError, "Not implemented: #{self.class}#write"
|
21
|
+
end
|
22
|
+
|
23
|
+
def read(path)
|
24
|
+
raise NoMethodError, "Not implemented: #{self.class}#read"
|
25
|
+
end
|
26
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
27
|
+
end
|
28
|
+
|
29
|
+
# A project based on real FS.
|
30
|
+
class Project < AbstractProject
|
31
|
+
attr_accessor :base_dir
|
32
|
+
|
33
|
+
def initialize(base_dir: Pathname(Dir.pwd))
|
34
|
+
super()
|
35
|
+
@base_dir = base_dir
|
36
|
+
end
|
37
|
+
|
38
|
+
def glob(
|
39
|
+
ext:,
|
40
|
+
include:,
|
41
|
+
exclude:,
|
42
|
+
&block
|
43
|
+
)
|
44
|
+
return enum_for(:glob, ext:, include:, exclude:) unless block
|
45
|
+
|
46
|
+
loaded = Set.new # : Set[String]
|
47
|
+
include.each do |incl_dir|
|
48
|
+
Dir.glob(
|
49
|
+
"#{incl_dir}/**/*#{ext}",
|
50
|
+
base: @base_dir
|
51
|
+
).sort.each do |path|
|
52
|
+
next unless File.file?(@base_dir + path)
|
53
|
+
next if loaded.include?(path)
|
54
|
+
|
55
|
+
loaded << path
|
56
|
+
is_excluded = exclude.any? do |excl_dir|
|
57
|
+
"#{path}/".start_with?("#{excl_dir}/")
|
58
|
+
end
|
59
|
+
block.(path) unless is_excluded
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def write(path, content)
|
65
|
+
full_path = @base_dir + path
|
66
|
+
FileUtils.mkdir_p(full_path.dirname)
|
67
|
+
File.write(full_path.to_s, content)
|
68
|
+
end
|
69
|
+
|
70
|
+
def read(path)
|
71
|
+
File.read((@base_dir + path).to_s)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# An in-memory project.
|
76
|
+
class FakeProject < AbstractProject
|
77
|
+
def initialize
|
78
|
+
super
|
79
|
+
@files = {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def glob(
|
83
|
+
ext:,
|
84
|
+
include:,
|
85
|
+
exclude:,
|
86
|
+
&block
|
87
|
+
)
|
88
|
+
return enum_for(:glob, ext:, include:, exclude:) unless block
|
89
|
+
|
90
|
+
@files.each_key do |path|
|
91
|
+
has_ext = path.end_with?(ext)
|
92
|
+
is_incl = include.any? do |incl_dir|
|
93
|
+
"#{path}/".start_with?("#{incl_dir}/")
|
94
|
+
end
|
95
|
+
is_excl = exclude.any? do |excl_dir|
|
96
|
+
"#{path}/".start_with?("#{excl_dir}/")
|
97
|
+
end
|
98
|
+
block.(path) if has_ext && is_incl && !is_excl
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def write(path, content)
|
103
|
+
@files[path] = content
|
104
|
+
end
|
105
|
+
|
106
|
+
def read(path)
|
107
|
+
@files[path] || raise(Errno::ENOENT, path)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/rbs_macros.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "rbs_macros/version"
|
4
|
+
require_relative "rbs_macros/config"
|
5
|
+
require_relative "rbs_macros/environment"
|
6
|
+
require_relative "rbs_macros/exec_ctx"
|
7
|
+
require_relative "rbs_macros/library_registry"
|
8
|
+
require_relative "rbs_macros/macro"
|
9
|
+
require_relative "rbs_macros/meta_module"
|
10
|
+
require_relative "rbs_macros/project"
|
11
|
+
|
12
|
+
require "stringio"
|
13
|
+
require "rbs"
|
14
|
+
|
15
|
+
# RbsMacros is a utility that looks for metaprogramming-related
|
16
|
+
# method invocation in your Ruby code and generates RBS files for them.
|
17
|
+
module RbsMacros
|
18
|
+
def self.run(&block)
|
19
|
+
config = Config.new
|
20
|
+
block&.(config)
|
21
|
+
|
22
|
+
env = Environment.new
|
23
|
+
config.loader.load(env: env.rbs) if config.use_loader
|
24
|
+
config.macros.each do |macro|
|
25
|
+
macro.setup(env)
|
26
|
+
end
|
27
|
+
|
28
|
+
config.project.glob(ext: ".rbs", include: config.sigs, exclude: [config.output_dir]) do |filename|
|
29
|
+
source = config.project.read(filename)
|
30
|
+
buffer = RBS::Buffer.new(name: filename, content: source)
|
31
|
+
_, directives, decls = RBS::Parser.parse_signature(buffer)
|
32
|
+
env.rbs.add_signature(buffer:, directives:, decls:)
|
33
|
+
end
|
34
|
+
# TODO: streamline this private method invocation
|
35
|
+
env.instance_variable_set(:@rbs, env.rbs.resolve_type_names)
|
36
|
+
config.project.glob(ext: ".rb", include: config.load_dirs, exclude: []) do |filename|
|
37
|
+
relative_filename = filename
|
38
|
+
config.load_dirs.each do |load_dir|
|
39
|
+
if filename.start_with?("#{load_dir}/")
|
40
|
+
relative_filename = filename.delete_prefix("#{load_dir}/")
|
41
|
+
break
|
42
|
+
end
|
43
|
+
end
|
44
|
+
source = config.project.read(filename)
|
45
|
+
env.meta_eval_ruby(source, filename: relative_filename.sub(/\.rb\z/, ""))
|
46
|
+
end
|
47
|
+
|
48
|
+
files = {} # : Hash[String, Array[RBS::AST::Declarations::t]]
|
49
|
+
env.decls.each do |entry|
|
50
|
+
file_decls = (files[entry.file] ||= [])
|
51
|
+
current_mod = env.object_class
|
52
|
+
container = nil # : (RBS::AST::Declarations::Module | RBS::AST::Declarations::Class)?
|
53
|
+
(entry.mod.name || "").split("::").each do |name|
|
54
|
+
inner_mod = current_mod.meta_const_get(name.to_sym)
|
55
|
+
raise "Not found: #{current_mod.name}::#{name}" unless inner_mod
|
56
|
+
raise "Not a module: #{current_mod.name}::#{name}" unless inner_mod.is_a?(MetaModule)
|
57
|
+
|
58
|
+
current_mod = inner_mod
|
59
|
+
|
60
|
+
current_decls = container&.members || file_decls
|
61
|
+
container = nil
|
62
|
+
current_decls.each do |decl|
|
63
|
+
if (decl.is_a?(RBS::AST::Declarations::Class) || decl.is_a?(RBS::AST::Declarations::Module)) \
|
64
|
+
&& decl.name.name == name.to_sym
|
65
|
+
container = decl
|
66
|
+
end
|
67
|
+
end
|
68
|
+
next if container
|
69
|
+
|
70
|
+
if inner_mod.is_class
|
71
|
+
container = c = RBS::AST::Declarations::Class.new(
|
72
|
+
name: RBS::TypeName.new(name: name.to_sym, namespace: RBS::Namespace.empty),
|
73
|
+
type_params: [],
|
74
|
+
super_class: nil,
|
75
|
+
members: [],
|
76
|
+
annotations: [],
|
77
|
+
location: nil,
|
78
|
+
comment: nil
|
79
|
+
)
|
80
|
+
current_decls << c
|
81
|
+
else
|
82
|
+
container = m = RBS::AST::Declarations::Module.new(
|
83
|
+
name: RBS::TypeName.new(name: name.to_sym, namespace: RBS::Namespace.empty),
|
84
|
+
type_params: [],
|
85
|
+
members: [],
|
86
|
+
self_types: [],
|
87
|
+
annotations: [],
|
88
|
+
location: nil,
|
89
|
+
comment: nil
|
90
|
+
)
|
91
|
+
current_decls << m
|
92
|
+
end
|
93
|
+
end
|
94
|
+
if container
|
95
|
+
container.members << entry.declaration
|
96
|
+
else
|
97
|
+
d = entry.declaration
|
98
|
+
case d
|
99
|
+
when RBS::AST::Declarations::Class,
|
100
|
+
RBS::AST::Declarations::Module,
|
101
|
+
RBS::AST::Declarations::Interface,
|
102
|
+
RBS::AST::Declarations::Constant,
|
103
|
+
RBS::AST::Declarations::Global,
|
104
|
+
RBS::AST::Declarations::TypeAlias,
|
105
|
+
RBS::AST::Declarations::ClassAlias,
|
106
|
+
RBS::AST::Declarations::ModuleAlias
|
107
|
+
file_decls << d
|
108
|
+
else
|
109
|
+
raise "Not allowed here: #{d.class}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
decls = [] # : Array[RBS::AST::Declarations::t]
|
115
|
+
env.object_class.meta_constants.each do |name|
|
116
|
+
value = env.object_class.meta_const_get(name)
|
117
|
+
next unless value.is_a?(MetaModule)
|
118
|
+
|
119
|
+
decls << RBS::AST::Declarations::Module.new(
|
120
|
+
name: RBS::TypeName.new(name:, namespace: RBS::Namespace.empty),
|
121
|
+
type_params: [],
|
122
|
+
members: [],
|
123
|
+
self_types: [],
|
124
|
+
annotations: [],
|
125
|
+
location: nil,
|
126
|
+
comment: nil
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
files.each do |filename, file_decls|
|
131
|
+
out = StringIO.new(+"", "w")
|
132
|
+
writer = RBS::Writer.new(out:)
|
133
|
+
writer.write file_decls
|
134
|
+
config.project.write("#{config.output_dir}/#{filename}.rbs", out.string)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|