docscribe 1.1.0 → 1.2.1
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 +662 -187
- data/exe/docscribe +2 -126
- data/lib/docscribe/cli/config_builder.rb +62 -0
- data/lib/docscribe/cli/init.rb +58 -0
- data/lib/docscribe/cli/options.rb +204 -0
- data/lib/docscribe/cli/run.rb +415 -0
- data/lib/docscribe/cli.rb +31 -0
- data/lib/docscribe/config/defaults.rb +71 -0
- data/lib/docscribe/config/emit.rb +142 -0
- data/lib/docscribe/config/filtering.rb +160 -0
- data/lib/docscribe/config/loader.rb +59 -0
- data/lib/docscribe/config/rbs.rb +51 -0
- data/lib/docscribe/config/sorbet.rb +87 -0
- data/lib/docscribe/config/sorting.rb +23 -0
- data/lib/docscribe/config/template.rb +184 -0
- data/lib/docscribe/config/utils.rb +102 -0
- data/lib/docscribe/config.rb +20 -230
- data/lib/docscribe/infer/ast_walk.rb +28 -0
- data/lib/docscribe/infer/constants.rb +11 -0
- data/lib/docscribe/infer/literals.rb +55 -0
- data/lib/docscribe/infer/names.rb +43 -0
- data/lib/docscribe/infer/params.rb +62 -0
- data/lib/docscribe/infer/raises.rb +68 -0
- data/lib/docscribe/infer/returns.rb +171 -0
- data/lib/docscribe/infer.rb +104 -258
- data/lib/docscribe/inline_rewriter/collector.rb +845 -0
- data/lib/docscribe/inline_rewriter/doc_block.rb +383 -0
- data/lib/docscribe/inline_rewriter/doc_builder.rb +607 -0
- data/lib/docscribe/inline_rewriter/source_helpers.rb +228 -0
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +244 -0
- data/lib/docscribe/inline_rewriter.rb +599 -428
- data/lib/docscribe/parsing.rb +55 -44
- data/lib/docscribe/types/provider_chain.rb +37 -0
- data/lib/docscribe/types/rbs/provider.rb +213 -0
- data/lib/docscribe/types/rbs/type_formatter.rb +132 -0
- data/lib/docscribe/types/signature.rb +65 -0
- data/lib/docscribe/types/sorbet/base_provider.rb +217 -0
- data/lib/docscribe/types/sorbet/rbi_provider.rb +35 -0
- data/lib/docscribe/types/sorbet/source_provider.rb +25 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +37 -3
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'parser/ast/processor'
|
|
4
|
+
|
|
5
|
+
module Docscribe
|
|
6
|
+
module InlineRewriter
|
|
7
|
+
# AST walker that collects documentation insertion targets.
|
|
8
|
+
#
|
|
9
|
+
# This is where Docscribe models Ruby scoping and visibility semantics so the
|
|
10
|
+
# doc generator can:
|
|
11
|
+
# - know whether a method is an instance method or class/module method (`#` vs `.`)
|
|
12
|
+
# - add `@private` / `@protected` tags when appropriate
|
|
13
|
+
# - know the container name (`A::B`) to show in `+A::B#foo+`
|
|
14
|
+
#
|
|
15
|
+
# In addition to `private` / `protected` / `public` handling, Collector
|
|
16
|
+
# supports:
|
|
17
|
+
# - `module_function` inside modules
|
|
18
|
+
# - `extend self` inside modules
|
|
19
|
+
# - receiver-based containers (`def Foo.bar`, `class << Foo`)
|
|
20
|
+
# - Sorbet-aware anchoring for methods with leading `sig` declarations
|
|
21
|
+
class Collector < Parser::AST::Processor
|
|
22
|
+
# One method that Docscribe intends to document.
|
|
23
|
+
#
|
|
24
|
+
# @!attribute node
|
|
25
|
+
# @return [Parser::AST::Node] the `:def` or `:defs` node
|
|
26
|
+
# @!attribute scope
|
|
27
|
+
# @return [Symbol] :instance or :class
|
|
28
|
+
# @!attribute visibility
|
|
29
|
+
# @return [Symbol] :public, :protected, or :private
|
|
30
|
+
# @!attribute container
|
|
31
|
+
# @return [String] container name, e.g. "MyModule::MyClass"
|
|
32
|
+
# @!attribute module_function
|
|
33
|
+
# @return [Boolean, nil] true if documented under module_function semantics
|
|
34
|
+
# @!attribute included_instance_visibility
|
|
35
|
+
# @return [Symbol, nil] included instance visibility under module_function
|
|
36
|
+
# @!attribute anchor_node
|
|
37
|
+
# @return [Parser::AST::Node] first leading Sorbet `sig` if present, else the method node
|
|
38
|
+
Insertion = Struct.new(:node, :scope, :visibility, :container, :module_function, :included_instance_visibility,
|
|
39
|
+
:anchor_node)
|
|
40
|
+
|
|
41
|
+
# One attribute macro call that Docscribe intends to document.
|
|
42
|
+
#
|
|
43
|
+
# This corresponds to an `attr_reader`, `attr_writer`, or `attr_accessor` call in Ruby source.
|
|
44
|
+
#
|
|
45
|
+
# @!attribute node
|
|
46
|
+
# @return [Parser::AST::Node] the `:send` node (e.g. `attr_reader :name`)
|
|
47
|
+
# @!attribute scope
|
|
48
|
+
# @return [Symbol] :instance or :class (class when inside `class << self`)
|
|
49
|
+
# @!attribute visibility
|
|
50
|
+
# @return [Symbol] :public, :protected, or :private
|
|
51
|
+
# @!attribute container
|
|
52
|
+
# @return [String] container name, e.g. "MyModule::MyClass"
|
|
53
|
+
# @!attribute access
|
|
54
|
+
# @return [Symbol] :r, :w, or :rw (reader/writer/accessor)
|
|
55
|
+
# @!attribute names
|
|
56
|
+
# @return [Array<Symbol>] attribute names
|
|
57
|
+
AttrInsertion = Struct.new(:node, :scope, :visibility, :container, :access, :names)
|
|
58
|
+
|
|
59
|
+
# Tracks visibility and container state while walking a class/module body.
|
|
60
|
+
#
|
|
61
|
+
# The context carries enough Ruby state to support:
|
|
62
|
+
# - lexical visibility changes
|
|
63
|
+
# - `class << self`
|
|
64
|
+
# - `module_function`
|
|
65
|
+
# - `extend self`
|
|
66
|
+
# - retroactive visibility updates
|
|
67
|
+
class VisibilityCtx
|
|
68
|
+
# @!attribute [rw] default_instance_vis
|
|
69
|
+
# @return [Object]
|
|
70
|
+
# @param value [Object]
|
|
71
|
+
attr_accessor :default_instance_vis
|
|
72
|
+
|
|
73
|
+
# @!attribute [rw] default_class_vis
|
|
74
|
+
# @return [Object]
|
|
75
|
+
# @param value [Object]
|
|
76
|
+
attr_accessor :default_class_vis
|
|
77
|
+
|
|
78
|
+
# @!attribute [rw] inside_sclass
|
|
79
|
+
# @return [Object]
|
|
80
|
+
# @param value [Object]
|
|
81
|
+
attr_accessor :inside_sclass
|
|
82
|
+
|
|
83
|
+
# @!attribute [rw] module_function_default
|
|
84
|
+
# @return [Object]
|
|
85
|
+
# @param value [Object]
|
|
86
|
+
attr_accessor :module_function_default
|
|
87
|
+
|
|
88
|
+
# @!attribute [rw] container_override
|
|
89
|
+
# @return [Object]
|
|
90
|
+
# @param value [Object]
|
|
91
|
+
attr_accessor :container_override
|
|
92
|
+
|
|
93
|
+
# @!attribute [r] explicit_instance
|
|
94
|
+
# @return [Object]
|
|
95
|
+
attr_reader :explicit_instance
|
|
96
|
+
|
|
97
|
+
# @!attribute [r] explicit_class
|
|
98
|
+
# @return [Object]
|
|
99
|
+
attr_reader :explicit_class
|
|
100
|
+
|
|
101
|
+
# @!attribute [r] module_function_explicit
|
|
102
|
+
# @return [Object]
|
|
103
|
+
attr_reader :module_function_explicit
|
|
104
|
+
|
|
105
|
+
# @!attribute [rw] container_is_module
|
|
106
|
+
# @return [Object]
|
|
107
|
+
# @param value [Object]
|
|
108
|
+
attr_accessor :container_is_module
|
|
109
|
+
|
|
110
|
+
# @!attribute [rw] extend_self
|
|
111
|
+
# @return [Object]
|
|
112
|
+
# @param value [Object]
|
|
113
|
+
attr_accessor :extend_self
|
|
114
|
+
|
|
115
|
+
# Create a fresh visibility context with Ruby-like defaults.
|
|
116
|
+
#
|
|
117
|
+
# @return [void]
|
|
118
|
+
def initialize
|
|
119
|
+
@default_instance_vis = :public
|
|
120
|
+
@default_class_vis = :public
|
|
121
|
+
@explicit_instance = {}
|
|
122
|
+
@explicit_class = {}
|
|
123
|
+
@inside_sclass = false
|
|
124
|
+
@module_function_default = false
|
|
125
|
+
@module_function_explicit = {}
|
|
126
|
+
@container_override = nil
|
|
127
|
+
@container_is_module = false
|
|
128
|
+
@extend_self = false
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Duplicate the context so nested bodies can mutate state independently.
|
|
132
|
+
#
|
|
133
|
+
# @return [VisibilityCtx]
|
|
134
|
+
def dup
|
|
135
|
+
c = VisibilityCtx.new
|
|
136
|
+
c.default_instance_vis = default_instance_vis
|
|
137
|
+
c.default_class_vis = default_class_vis
|
|
138
|
+
c.inside_sclass = inside_sclass
|
|
139
|
+
|
|
140
|
+
c.module_function_default = module_function_default
|
|
141
|
+
c.module_function_explicit.merge!(module_function_explicit)
|
|
142
|
+
|
|
143
|
+
c.explicit_instance.merge!(explicit_instance)
|
|
144
|
+
c.explicit_class.merge!(explicit_class)
|
|
145
|
+
|
|
146
|
+
c.container_override = container_override
|
|
147
|
+
c.container_is_module = container_is_module
|
|
148
|
+
c.extend_self = extend_self
|
|
149
|
+
c
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @!attribute [r] insertions
|
|
154
|
+
# @return [Array<Insertion>]
|
|
155
|
+
attr_reader :insertions
|
|
156
|
+
|
|
157
|
+
# @!attribute [r] attr_insertions
|
|
158
|
+
# @return [Array<AttrInsertion>]
|
|
159
|
+
attr_reader :attr_insertions
|
|
160
|
+
|
|
161
|
+
# Method documentation.
|
|
162
|
+
#
|
|
163
|
+
# @param [Parser::Source::Buffer] buffer
|
|
164
|
+
# @return [Object]
|
|
165
|
+
def initialize(buffer)
|
|
166
|
+
super()
|
|
167
|
+
@buffer = buffer
|
|
168
|
+
@insertions = []
|
|
169
|
+
@attr_insertions = []
|
|
170
|
+
@name_stack = []
|
|
171
|
+
|
|
172
|
+
# Track module-level state across reopened modules within the same file pass.
|
|
173
|
+
# Example:
|
|
174
|
+
# module M; extend self; end
|
|
175
|
+
# module M; def foo; end; end # => should still document foo as M.foo
|
|
176
|
+
#
|
|
177
|
+
# @type [Hash{String=>Hash}]
|
|
178
|
+
@module_states = {} # { "M" => { extend_self: true } }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Enter a class body and collect documentation targets from its contents.
|
|
182
|
+
#
|
|
183
|
+
# @param [Parser::AST::Node] node
|
|
184
|
+
# @return [Parser::AST::Node]
|
|
185
|
+
def on_class(node)
|
|
186
|
+
cname_node, super_node, body = *node
|
|
187
|
+
@name_stack.push(const_name(cname_node))
|
|
188
|
+
|
|
189
|
+
ctx = VisibilityCtx.new
|
|
190
|
+
ctx.container_is_module = false
|
|
191
|
+
|
|
192
|
+
process_struct_class(node, super_node)
|
|
193
|
+
process_body(body, ctx)
|
|
194
|
+
|
|
195
|
+
@name_stack.pop
|
|
196
|
+
node
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Enter a module body and collect documentation targets from its contents.
|
|
200
|
+
#
|
|
201
|
+
# This also carries `extend self` state across reopened modules in the same
|
|
202
|
+
# file.
|
|
203
|
+
#
|
|
204
|
+
# @param [Parser::AST::Node] node
|
|
205
|
+
# @return [Parser::AST::Node]
|
|
206
|
+
def on_module(node)
|
|
207
|
+
cname_node, body = *node
|
|
208
|
+
@name_stack.push(const_name(cname_node))
|
|
209
|
+
|
|
210
|
+
container = current_container
|
|
211
|
+
|
|
212
|
+
ctx = VisibilityCtx.new
|
|
213
|
+
ctx.container_is_module = true
|
|
214
|
+
ctx.extend_self = !!@module_states.dig(container, :extend_self)
|
|
215
|
+
|
|
216
|
+
process_body(body, ctx)
|
|
217
|
+
|
|
218
|
+
# If `extend self` is active for this module, document all instance defs as module methods (M.foo).
|
|
219
|
+
if ctx.extend_self
|
|
220
|
+
promote_extend_self_container(container: container)
|
|
221
|
+
@module_states[container] ||= {}
|
|
222
|
+
@module_states[container][:extend_self] = true
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
@name_stack.pop
|
|
226
|
+
node
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Method documentation.
|
|
230
|
+
#
|
|
231
|
+
# @param [Object] node Param documentation.
|
|
232
|
+
# @return [Object]
|
|
233
|
+
def on_casgn(node)
|
|
234
|
+
return node if process_struct_casgn(node)
|
|
235
|
+
|
|
236
|
+
node.children.each do |child|
|
|
237
|
+
process(child) if child.is_a?(Parser::AST::Node)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
node
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
private
|
|
244
|
+
|
|
245
|
+
# Method documentation.
|
|
246
|
+
#
|
|
247
|
+
# @private
|
|
248
|
+
# @param [Object] node Param documentation.
|
|
249
|
+
# @param [Object] ctx Param documentation.
|
|
250
|
+
# @param [nil] pending_sig_anchor Param documentation.
|
|
251
|
+
# @return [Object]
|
|
252
|
+
def process_stmt(node, ctx, pending_sig_anchor: nil)
|
|
253
|
+
return unless node
|
|
254
|
+
|
|
255
|
+
case node.type
|
|
256
|
+
when :def
|
|
257
|
+
name, _args, _body = *node
|
|
258
|
+
anchor_node = pending_sig_anchor || node
|
|
259
|
+
|
|
260
|
+
if module_function_applies?(ctx, name)
|
|
261
|
+
scope = :class
|
|
262
|
+
vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
263
|
+
|
|
264
|
+
# module_function makes included instance method private by default,
|
|
265
|
+
# but explicit named visibility can override it (e.g. `public :foo`).
|
|
266
|
+
included_vis = ctx.explicit_instance[name] || :private
|
|
267
|
+
|
|
268
|
+
@insertions << Insertion.new(node, scope, vis, container_for(ctx), true, included_vis, anchor_node)
|
|
269
|
+
return
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
if extend_self_applies?(ctx)
|
|
273
|
+
# Under `extend self` in a module, instance methods are callable as module methods (M.foo).
|
|
274
|
+
scope = :class
|
|
275
|
+
vis = ctx.explicit_instance[name] || ctx.default_instance_vis
|
|
276
|
+
|
|
277
|
+
@insertions << Insertion.new(node, scope, vis, container_for(ctx), nil, nil, anchor_node)
|
|
278
|
+
return
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# existing behavior for non-module_function:
|
|
282
|
+
if ctx.inside_sclass
|
|
283
|
+
vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
284
|
+
scope = :class
|
|
285
|
+
else
|
|
286
|
+
vis = ctx.explicit_instance[name] || ctx.default_instance_vis
|
|
287
|
+
scope = :instance
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
@insertions << Insertion.new(node, scope, vis, container_for(ctx), nil, nil, anchor_node)
|
|
291
|
+
|
|
292
|
+
when :defs
|
|
293
|
+
recv, name, _args, _body = *node
|
|
294
|
+
vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
295
|
+
|
|
296
|
+
container =
|
|
297
|
+
if const_receiver?(recv)
|
|
298
|
+
const_name(recv)
|
|
299
|
+
else
|
|
300
|
+
container_for(ctx)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
@insertions << Insertion.new(node, :class, vis, container, nil, nil, pending_sig_anchor || node)
|
|
304
|
+
|
|
305
|
+
when :sclass
|
|
306
|
+
# `class << self` — affects default visibility for singleton methods and changes scope.
|
|
307
|
+
recv, body = *node
|
|
308
|
+
inner_ctx = ctx.dup
|
|
309
|
+
|
|
310
|
+
if self_node?(recv)
|
|
311
|
+
# class << self
|
|
312
|
+
inner_ctx.inside_sclass = true
|
|
313
|
+
inner_ctx.container_override = nil
|
|
314
|
+
elsif const_receiver?(recv)
|
|
315
|
+
# class << Foo (const receiver) — document methods under Foo
|
|
316
|
+
inner_ctx.inside_sclass = true
|
|
317
|
+
inner_ctx.container_override = const_name(recv)
|
|
318
|
+
else
|
|
319
|
+
# Unknown receiver (e.g. class << obj) — keep prior behavior
|
|
320
|
+
inner_ctx.inside_sclass = false
|
|
321
|
+
inner_ctx.container_override = nil
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# NOTE: we intentionally do NOT reset default_class_vis here; we inherit via ctx.dup.
|
|
325
|
+
process_body(body, inner_ctx)
|
|
326
|
+
|
|
327
|
+
when :casgn
|
|
328
|
+
if process_struct_casgn(node)
|
|
329
|
+
# handled
|
|
330
|
+
else
|
|
331
|
+
process(node)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
when :send
|
|
335
|
+
if process_attr_send(node, ctx)
|
|
336
|
+
# handled
|
|
337
|
+
elsif process_extend_self_send(node, ctx)
|
|
338
|
+
# handled
|
|
339
|
+
elsif process_module_function_send(node, ctx)
|
|
340
|
+
# handled
|
|
341
|
+
elsif process_class_method_visibility_send(node, ctx)
|
|
342
|
+
# handled
|
|
343
|
+
else
|
|
344
|
+
process_visibility_send(node, ctx, pending_sig_anchor: pending_sig_anchor)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
else
|
|
348
|
+
process(node)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Method documentation.
|
|
353
|
+
#
|
|
354
|
+
# @private
|
|
355
|
+
# @param [Object] node Param documentation.
|
|
356
|
+
# @param [Object] super_node Param documentation.
|
|
357
|
+
# @return [Object]
|
|
358
|
+
def process_struct_class(node, super_node)
|
|
359
|
+
return unless struct_new_node?(super_node)
|
|
360
|
+
|
|
361
|
+
names = extract_struct_member_names(super_node)
|
|
362
|
+
return if names.empty?
|
|
363
|
+
|
|
364
|
+
@attr_insertions << AttrInsertion.new(
|
|
365
|
+
node, # insert above the class declaration
|
|
366
|
+
:instance, # struct members are instance readers/writers
|
|
367
|
+
:public, # Struct fields are public by default
|
|
368
|
+
current_container,
|
|
369
|
+
:rw,
|
|
370
|
+
names
|
|
371
|
+
)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Method documentation.
|
|
375
|
+
#
|
|
376
|
+
# @private
|
|
377
|
+
# @param [Object] node Param documentation.
|
|
378
|
+
# @return [Boolean]
|
|
379
|
+
def process_struct_casgn(node)
|
|
380
|
+
_scope, _name, value = *node
|
|
381
|
+
return false unless struct_new_node?(value)
|
|
382
|
+
|
|
383
|
+
names = extract_struct_member_names(value)
|
|
384
|
+
return true if names.empty?
|
|
385
|
+
|
|
386
|
+
@attr_insertions << AttrInsertion.new(
|
|
387
|
+
node, # insert above the constant assignment
|
|
388
|
+
:instance,
|
|
389
|
+
:public,
|
|
390
|
+
struct_container_name(node),
|
|
391
|
+
:rw,
|
|
392
|
+
names
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
true
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Method documentation.
|
|
399
|
+
#
|
|
400
|
+
# @private
|
|
401
|
+
# @param [Object] node Param documentation.
|
|
402
|
+
# @return [Object]
|
|
403
|
+
def struct_new_node?(node)
|
|
404
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
405
|
+
return false unless node.type == :send
|
|
406
|
+
|
|
407
|
+
recv, meth, *_args = *node
|
|
408
|
+
return false unless meth == :new
|
|
409
|
+
return false unless recv&.type == :const
|
|
410
|
+
|
|
411
|
+
recv_name = const_name(recv)
|
|
412
|
+
%w[Struct ::Struct].include?(recv_name)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Method documentation.
|
|
416
|
+
#
|
|
417
|
+
# @private
|
|
418
|
+
# @param [Object] struct_new_node Param documentation.
|
|
419
|
+
# @return [Object]
|
|
420
|
+
def extract_struct_member_names(struct_new_node)
|
|
421
|
+
_recv, _meth, *args = *struct_new_node
|
|
422
|
+
|
|
423
|
+
# Drop trailing keyword/options hash, e.g. keyword_init: true
|
|
424
|
+
args = args.reject { |arg| arg.is_a?(Parser::AST::Node) && arg.type == :hash }
|
|
425
|
+
|
|
426
|
+
# Support Struct.new("Foo", :a, :b)
|
|
427
|
+
args = args.drop(1) if args.length >= 2 && args.first.is_a?(Parser::AST::Node) && args.first.type == :str
|
|
428
|
+
|
|
429
|
+
args.map { |arg| extract_name_sym(arg) }.compact
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Method documentation.
|
|
433
|
+
#
|
|
434
|
+
# @private
|
|
435
|
+
# @param [Object] node Param documentation.
|
|
436
|
+
# @return [Object]
|
|
437
|
+
def struct_container_name(node)
|
|
438
|
+
scope, name, _value = *node
|
|
439
|
+
|
|
440
|
+
prefix =
|
|
441
|
+
if scope
|
|
442
|
+
const_name(scope)
|
|
443
|
+
elsif current_container == 'Object'
|
|
444
|
+
nil
|
|
445
|
+
else
|
|
446
|
+
current_container
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
[prefix, name.to_s].compact.reject(&:empty?).join('::')
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Method documentation.
|
|
453
|
+
#
|
|
454
|
+
# @private
|
|
455
|
+
# @param [Object] node Param documentation.
|
|
456
|
+
# @param [Object] ctx Param documentation.
|
|
457
|
+
# @return [Boolean]
|
|
458
|
+
def process_extend_self_send(node, ctx)
|
|
459
|
+
recv, meth, *args = *node
|
|
460
|
+
|
|
461
|
+
return false unless ctx.container_is_module
|
|
462
|
+
return false unless recv.nil? && meth == :extend
|
|
463
|
+
return false if ctx.inside_sclass
|
|
464
|
+
return false unless args.any? { |a| self_node?(a) }
|
|
465
|
+
|
|
466
|
+
ctx.extend_self = true
|
|
467
|
+
|
|
468
|
+
# Persist across reopened modules in this file.
|
|
469
|
+
container = container_for(ctx)
|
|
470
|
+
@module_states[container] ||= {}
|
|
471
|
+
@module_states[container][:extend_self] = true
|
|
472
|
+
|
|
473
|
+
true
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# Method documentation.
|
|
477
|
+
#
|
|
478
|
+
# @private
|
|
479
|
+
# @param [Object] ctx Param documentation.
|
|
480
|
+
# @return [Object]
|
|
481
|
+
def extend_self_applies?(ctx)
|
|
482
|
+
ctx.container_is_module && ctx.extend_self && !ctx.inside_sclass
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Method documentation.
|
|
486
|
+
#
|
|
487
|
+
# @private
|
|
488
|
+
# @param [Object] node Param documentation.
|
|
489
|
+
# @return [Object]
|
|
490
|
+
def const_receiver?(node)
|
|
491
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
492
|
+
|
|
493
|
+
%i[const cbase].include?(node.type)
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Method documentation.
|
|
497
|
+
#
|
|
498
|
+
# @private
|
|
499
|
+
# @param [Object] node Param documentation.
|
|
500
|
+
# @param [Object] ctx Param documentation.
|
|
501
|
+
# @return [Boolean]
|
|
502
|
+
def process_attr_send(node, ctx)
|
|
503
|
+
recv, meth, *args = *node
|
|
504
|
+
return false unless recv.nil? && %i[attr_reader attr_writer attr_accessor].include?(meth)
|
|
505
|
+
|
|
506
|
+
names = args.map { |a| extract_name_sym(a) }.compact
|
|
507
|
+
return true if names.empty?
|
|
508
|
+
|
|
509
|
+
scope = ctx.inside_sclass ? :class : :instance
|
|
510
|
+
visibility = ctx.inside_sclass ? ctx.default_class_vis : ctx.default_instance_vis
|
|
511
|
+
|
|
512
|
+
access =
|
|
513
|
+
case meth
|
|
514
|
+
when :attr_reader then :r
|
|
515
|
+
when :attr_writer then :w
|
|
516
|
+
else :rw
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
@attr_insertions << AttrInsertion.new(node, scope, visibility, container_for(ctx), access, names)
|
|
520
|
+
|
|
521
|
+
true
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# Method documentation.
|
|
525
|
+
#
|
|
526
|
+
# @private
|
|
527
|
+
# @param [Object] node Param documentation.
|
|
528
|
+
# @param [Object] ctx Param documentation.
|
|
529
|
+
# @return [Boolean]
|
|
530
|
+
def process_class_method_visibility_send(node, ctx)
|
|
531
|
+
recv, meth, *args = *node
|
|
532
|
+
|
|
533
|
+
return false unless %i[private_class_method protected_class_method public_class_method].include?(meth)
|
|
534
|
+
return false unless recv.nil? || self_node?(recv)
|
|
535
|
+
|
|
536
|
+
visibility =
|
|
537
|
+
case meth
|
|
538
|
+
when :private_class_method then :private
|
|
539
|
+
when :protected_class_method then :protected
|
|
540
|
+
else :public
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
container = container_for(ctx)
|
|
544
|
+
|
|
545
|
+
args.each do |arg|
|
|
546
|
+
sym = extract_name_sym(arg)
|
|
547
|
+
next unless sym
|
|
548
|
+
|
|
549
|
+
ctx.explicit_class[sym] = visibility
|
|
550
|
+
retroactively_set_visibility(sym, visibility, scope: :class, container: container)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
true
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Method documentation.
|
|
557
|
+
#
|
|
558
|
+
# @private
|
|
559
|
+
# @param [Object] node Param documentation.
|
|
560
|
+
# @param [Object] ctx Param documentation.
|
|
561
|
+
# @param [nil] pending_sig_anchor Param documentation.
|
|
562
|
+
# @return [Object]
|
|
563
|
+
def process_visibility_send(node, ctx, pending_sig_anchor: nil)
|
|
564
|
+
recv, meth, *args = *node
|
|
565
|
+
return unless recv.nil? && %i[private protected public].include?(meth)
|
|
566
|
+
|
|
567
|
+
container = container_for(ctx)
|
|
568
|
+
|
|
569
|
+
if args.empty?
|
|
570
|
+
if ctx.inside_sclass
|
|
571
|
+
ctx.default_class_vis = meth
|
|
572
|
+
else
|
|
573
|
+
ctx.default_instance_vis = meth
|
|
574
|
+
end
|
|
575
|
+
return
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Inline modifier: private def foo / private def self.foo
|
|
579
|
+
if args.length == 1 && args[0].is_a?(Parser::AST::Node) && %i[def defs].include?(args[0].type)
|
|
580
|
+
def_node = args[0]
|
|
581
|
+
anchor_node = pending_sig_anchor || def_node
|
|
582
|
+
|
|
583
|
+
case def_node.type
|
|
584
|
+
when :def
|
|
585
|
+
name, = *def_node
|
|
586
|
+
|
|
587
|
+
if module_function_applies?(ctx, name)
|
|
588
|
+
mod_vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
589
|
+
included_vis = meth
|
|
590
|
+
@insertions << Insertion.new(def_node, :class, mod_vis, container, true, included_vis, anchor_node)
|
|
591
|
+
elsif ctx.inside_sclass
|
|
592
|
+
@insertions << Insertion.new(def_node, :class, meth, container, nil, nil, anchor_node)
|
|
593
|
+
else
|
|
594
|
+
@insertions << Insertion.new(def_node, :instance, meth, container, nil, nil, anchor_node)
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
return
|
|
598
|
+
|
|
599
|
+
when :defs
|
|
600
|
+
@insertions << Insertion.new(def_node, :class, meth, container, nil, nil, anchor_node)
|
|
601
|
+
return
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# Named visibility: private :foo
|
|
606
|
+
args.each do |arg|
|
|
607
|
+
sym = extract_name_sym(arg)
|
|
608
|
+
next unless sym
|
|
609
|
+
|
|
610
|
+
if ctx.inside_sclass
|
|
611
|
+
ctx.explicit_class[sym] = meth
|
|
612
|
+
retroactively_set_visibility(sym, meth, scope: :class, container: container)
|
|
613
|
+
else
|
|
614
|
+
ctx.explicit_instance[sym] = meth
|
|
615
|
+
retroactively_set_visibility(sym, meth, scope: :instance, container: container)
|
|
616
|
+
retroactively_set_included_instance_visibility_for_module_function(sym, meth, container: container)
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
# Method documentation.
|
|
622
|
+
#
|
|
623
|
+
# @private
|
|
624
|
+
# @param [Object] name_sym Param documentation.
|
|
625
|
+
# @param [Object] visibility Param documentation.
|
|
626
|
+
# @param [Object] container Param documentation.
|
|
627
|
+
# @return [Object]
|
|
628
|
+
def retroactively_set_included_instance_visibility_for_module_function(name_sym, visibility, container:)
|
|
629
|
+
@insertions.reverse_each do |ins|
|
|
630
|
+
next unless ins.container == container
|
|
631
|
+
next unless ins.module_function
|
|
632
|
+
next unless ins.node.type == :def
|
|
633
|
+
next unless ins.node.children[0] == name_sym
|
|
634
|
+
|
|
635
|
+
ins.included_instance_visibility = visibility
|
|
636
|
+
break
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# Method documentation.
|
|
641
|
+
#
|
|
642
|
+
# @private
|
|
643
|
+
# @param [Object] name_sym Param documentation.
|
|
644
|
+
# @param [Object] visibility Param documentation.
|
|
645
|
+
# @param [Object] scope Param documentation.
|
|
646
|
+
# @param [Object] container Param documentation.
|
|
647
|
+
# @return [Object]
|
|
648
|
+
def retroactively_set_visibility(name_sym, visibility, scope:, container:)
|
|
649
|
+
@insertions.reverse_each do |ins|
|
|
650
|
+
next unless ins.container == container
|
|
651
|
+
next unless ins.scope == scope
|
|
652
|
+
|
|
653
|
+
n = ins.node
|
|
654
|
+
method_name =
|
|
655
|
+
case n.type
|
|
656
|
+
when :def then n.children[0]
|
|
657
|
+
when :defs then n.children[1]
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
next unless method_name == name_sym
|
|
661
|
+
|
|
662
|
+
ins.visibility = visibility
|
|
663
|
+
break
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# Method documentation.
|
|
668
|
+
#
|
|
669
|
+
# @private
|
|
670
|
+
# @param [Object] ctx Param documentation.
|
|
671
|
+
# @param [Object] name Param documentation.
|
|
672
|
+
# @return [Object]
|
|
673
|
+
def module_function_applies?(ctx, name)
|
|
674
|
+
return false if ctx.inside_sclass
|
|
675
|
+
|
|
676
|
+
ctx.module_function_default || ctx.module_function_explicit[name]
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# Method documentation.
|
|
680
|
+
#
|
|
681
|
+
# @private
|
|
682
|
+
# @param [Object] node Param documentation.
|
|
683
|
+
# @param [Object] ctx Param documentation.
|
|
684
|
+
# @return [Boolean]
|
|
685
|
+
def process_module_function_send(node, ctx)
|
|
686
|
+
recv, meth, *args = *node
|
|
687
|
+
return false unless recv.nil? && meth == :module_function
|
|
688
|
+
return true if ctx.inside_sclass
|
|
689
|
+
|
|
690
|
+
if args.empty?
|
|
691
|
+
ctx.module_function_default = true
|
|
692
|
+
return true
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
names = args.map { |arg| extract_name_sym(arg) }.compact
|
|
696
|
+
names.each do |sym|
|
|
697
|
+
ctx.module_function_explicit[sym] = true
|
|
698
|
+
retroactively_promote_module_function(sym, container: container_for(ctx))
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
true
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# Method documentation.
|
|
705
|
+
#
|
|
706
|
+
# @private
|
|
707
|
+
# @param [Object] ctx Param documentation.
|
|
708
|
+
# @return [Object]
|
|
709
|
+
def container_for(ctx)
|
|
710
|
+
ctx.container_override || current_container
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
# Method documentation.
|
|
714
|
+
#
|
|
715
|
+
# @private
|
|
716
|
+
# @param [Object] name_sym Param documentation.
|
|
717
|
+
# @param [Object] container Param documentation.
|
|
718
|
+
# @return [Object]
|
|
719
|
+
def retroactively_promote_module_function(name_sym, container:)
|
|
720
|
+
@insertions.reverse_each do |ins|
|
|
721
|
+
next unless ins.container == container
|
|
722
|
+
next unless ins.node.type == :def
|
|
723
|
+
next unless ins.node.children[0] == name_sym
|
|
724
|
+
|
|
725
|
+
ins.scope = :class
|
|
726
|
+
ins.visibility = :public
|
|
727
|
+
ins.module_function = true
|
|
728
|
+
ins.included_instance_visibility ||= :private
|
|
729
|
+
break
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
# Method documentation.
|
|
734
|
+
#
|
|
735
|
+
# @private
|
|
736
|
+
# @param [Object] node Param documentation.
|
|
737
|
+
# @return [Object]
|
|
738
|
+
def self_node?(node)
|
|
739
|
+
node && node.type == :self
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# Method documentation.
|
|
743
|
+
#
|
|
744
|
+
# @private
|
|
745
|
+
# @param [Object] arg Param documentation.
|
|
746
|
+
# @return [Object]
|
|
747
|
+
def extract_name_sym(arg)
|
|
748
|
+
case arg.type
|
|
749
|
+
when :sym then arg.children.first
|
|
750
|
+
when :str then arg.children.first.to_sym
|
|
751
|
+
end
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
# Method documentation.
|
|
755
|
+
#
|
|
756
|
+
# @private
|
|
757
|
+
# @param [Object] node Param documentation.
|
|
758
|
+
# @return [Object]
|
|
759
|
+
def const_name(node)
|
|
760
|
+
return 'Object' unless node
|
|
761
|
+
|
|
762
|
+
case node.type
|
|
763
|
+
when :const
|
|
764
|
+
scope, name = *node
|
|
765
|
+
scope_name = scope ? const_name(scope) : nil
|
|
766
|
+
[scope_name, name].compact.join('::')
|
|
767
|
+
when :cbase
|
|
768
|
+
''
|
|
769
|
+
else
|
|
770
|
+
node.loc.expression.source
|
|
771
|
+
end
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
# Method documentation.
|
|
775
|
+
#
|
|
776
|
+
# @private
|
|
777
|
+
# @return [Object]
|
|
778
|
+
def current_container
|
|
779
|
+
@name_stack.empty? ? 'Object' : @name_stack.join('::')
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
# Method documentation.
|
|
783
|
+
#
|
|
784
|
+
# @private
|
|
785
|
+
# @param [Object] body Param documentation.
|
|
786
|
+
# @param [Object] ctx Param documentation.
|
|
787
|
+
# @return [Object]
|
|
788
|
+
def process_body(body, ctx)
|
|
789
|
+
return unless body
|
|
790
|
+
|
|
791
|
+
nodes = body.type == :begin ? body.children : [body]
|
|
792
|
+
pending_sig_nodes = []
|
|
793
|
+
|
|
794
|
+
nodes.each do |child|
|
|
795
|
+
if sorbet_sig_node?(child)
|
|
796
|
+
pending_sig_nodes << child
|
|
797
|
+
next
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
process_stmt(child, ctx, pending_sig_anchor: pending_sig_nodes.first)
|
|
801
|
+
pending_sig_nodes.clear
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
# Method documentation.
|
|
806
|
+
#
|
|
807
|
+
# @private
|
|
808
|
+
# @param [Object] node Param documentation.
|
|
809
|
+
# @return [Object]
|
|
810
|
+
def sorbet_sig_node?(node)
|
|
811
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
812
|
+
|
|
813
|
+
case node.type
|
|
814
|
+
when :send
|
|
815
|
+
recv, meth, *_args = *node
|
|
816
|
+
recv.nil? && meth == :sig
|
|
817
|
+
when :block
|
|
818
|
+
send_node, *_rest = *node
|
|
819
|
+
return false unless send_node&.type == :send
|
|
820
|
+
|
|
821
|
+
recv, meth, *_args = *send_node
|
|
822
|
+
recv.nil? && meth == :sig
|
|
823
|
+
else
|
|
824
|
+
false
|
|
825
|
+
end
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
# Method documentation.
|
|
829
|
+
#
|
|
830
|
+
# @private
|
|
831
|
+
# @param [Object] container Param documentation.
|
|
832
|
+
# @return [Object]
|
|
833
|
+
def promote_extend_self_container(container:)
|
|
834
|
+
@insertions.each do |ins|
|
|
835
|
+
next unless ins.container == container
|
|
836
|
+
next unless ins.node.type == :def
|
|
837
|
+
next unless ins.scope == :instance
|
|
838
|
+
next if ins.module_function
|
|
839
|
+
|
|
840
|
+
ins.scope = :class
|
|
841
|
+
end
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
end
|
|
845
|
+
end
|