docscribe 1.5.1 → 1.6.0
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 +197 -75
- data/exe/docscribe-client +51 -1
- data/lib/docscribe/cli/config_dump.rb +70 -0
- data/lib/docscribe/cli/coverage.rb +313 -0
- data/lib/docscribe/cli/init.rb +81 -4
- data/lib/docscribe/cli/options.rb +16 -0
- data/lib/docscribe/cli/rbs_gen.rb +3 -2
- data/lib/docscribe/cli/run.rb +134 -0
- data/lib/docscribe/cli/sigs.rb +1 -1
- data/lib/docscribe/cli.rb +7 -5
- data/lib/docscribe/config.rb +10 -0
- data/lib/docscribe/infer/behavior.rb +96 -0
- data/lib/docscribe/infer/params.rb +2 -4
- data/lib/docscribe/infer/returns.rb +1 -1
- data/lib/docscribe/infer.rb +35 -0
- data/lib/docscribe/inline_rewriter/doc_builder.rb +23 -10
- data/lib/docscribe/inline_rewriter.rb +142 -20
- data/lib/docscribe/server.rb +258 -29
- data/lib/docscribe/types/overload_selector.rb +79 -0
- data/lib/docscribe/types/provider_chain.rb +25 -2
- data/lib/docscribe/types/signature.rb +5 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +7 -3
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Infer
|
|
5
|
+
# Analyzes method behavior (predicates, side effects, mutating calls).
|
|
6
|
+
module Behavior
|
|
7
|
+
MUTATING_METHODS = %i[<< push pop delete merge update write save create destroy
|
|
8
|
+
insert update_all delete_all].freeze
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# @param [Object] body
|
|
12
|
+
# @param [Object] method_name
|
|
13
|
+
# @return [Hash<Symbol, Object>]
|
|
14
|
+
def analyze(body, method_name)
|
|
15
|
+
result = default_result(method_name)
|
|
16
|
+
|
|
17
|
+
return result unless body.is_a?(Parser::AST::Node)
|
|
18
|
+
|
|
19
|
+
analyze_body(body, result)
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param [Object] method_name
|
|
24
|
+
# @return [Hash<Symbol, Object>]
|
|
25
|
+
def default_result(method_name)
|
|
26
|
+
{
|
|
27
|
+
predicate: method_name&.to_s&.end_with?('?') || false,
|
|
28
|
+
bang: method_name&.to_s&.end_with?('!') || false,
|
|
29
|
+
has_side_effects: false,
|
|
30
|
+
delegates: nil,
|
|
31
|
+
returns_self: false,
|
|
32
|
+
returns_boolean: false
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param [Hash<Symbol, Object>] analysis
|
|
37
|
+
# @param [Object] _method_name
|
|
38
|
+
# @return [String?]
|
|
39
|
+
def infer_description(analysis, _method_name)
|
|
40
|
+
return nil unless analysis[:has_side_effects] || analysis[:predicate]
|
|
41
|
+
|
|
42
|
+
if analysis[:predicate]
|
|
43
|
+
'Returns true if the condition is met, false otherwise'
|
|
44
|
+
elsif analysis[:returns_self]
|
|
45
|
+
'Returns self to allow method chaining'
|
|
46
|
+
elsif analysis[:has_side_effects]
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# @private
|
|
54
|
+
# @param [Object] node
|
|
55
|
+
# @param [Hash<Symbol, Object>] result
|
|
56
|
+
# @return [void]
|
|
57
|
+
def analyze_body(node, result)
|
|
58
|
+
case node.type
|
|
59
|
+
when :ivasgn, :ivar
|
|
60
|
+
result[:has_side_effects] = true
|
|
61
|
+
when :send
|
|
62
|
+
analyze_send(node, result)
|
|
63
|
+
when :self
|
|
64
|
+
result[:returns_self] = true if result[:returns_self]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
recurse_children(node, result)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @private
|
|
71
|
+
# @param [Object] node
|
|
72
|
+
# @param [Hash<Symbol, Object>] result
|
|
73
|
+
# @return [void]
|
|
74
|
+
def recurse_children(node, result)
|
|
75
|
+
node.children.each do |child|
|
|
76
|
+
analyze_body(child, result) if child.is_a?(Parser::AST::Node)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @private
|
|
81
|
+
# @param [Object] node
|
|
82
|
+
# @param [Hash<Symbol, Object>] result
|
|
83
|
+
# @return [void]
|
|
84
|
+
def analyze_send(node, result)
|
|
85
|
+
_receiver, method_name = *node
|
|
86
|
+
method_sym = method_name.is_a?(Symbol) ? method_name : nil
|
|
87
|
+
return unless method_sym
|
|
88
|
+
|
|
89
|
+
return unless MUTATING_METHODS.include?(method_sym)
|
|
90
|
+
|
|
91
|
+
result[:has_side_effects] = true
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -48,9 +48,7 @@ module Docscribe
|
|
|
48
48
|
# @param [Boolean] treat_options_keyword_as_hash whether to treat 'options:' as Hash
|
|
49
49
|
# @return [String]
|
|
50
50
|
def inferred_param_type(name, default_str, fallback_type, treat_options_keyword_as_hash:)
|
|
51
|
-
if name.end_with?(':') && default_str.nil?
|
|
52
|
-
return options_keyword_type(name, treat_options_keyword_as_hash, fallback_type)
|
|
53
|
-
end
|
|
51
|
+
return options_keyword_type(name, treat_options_keyword_as_hash, fallback_type) if name.end_with?(':') && default_str.nil?
|
|
54
52
|
|
|
55
53
|
node = parse_expr(default_str)
|
|
56
54
|
ty = Literals.type_from_literal(node, fallback_type: fallback_type)
|
|
@@ -98,7 +96,7 @@ module Docscribe
|
|
|
98
96
|
buffer = Parser::Source::Buffer.new('(param)')
|
|
99
97
|
buffer.source = src
|
|
100
98
|
Docscribe::Parsing.parse_buffer(buffer)
|
|
101
|
-
rescue Parser::SyntaxError
|
|
99
|
+
rescue Parser::SyntaxError
|
|
102
100
|
nil
|
|
103
101
|
end
|
|
104
102
|
end
|
|
@@ -26,7 +26,7 @@ module Docscribe
|
|
|
26
26
|
local_var_types = build_local_variable_types(body)
|
|
27
27
|
run_last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true,
|
|
28
28
|
local_var_types: local_var_types) || FALLBACK_TYPE
|
|
29
|
-
rescue Parser::SyntaxError
|
|
29
|
+
rescue Parser::SyntaxError
|
|
30
30
|
FALLBACK_TYPE
|
|
31
31
|
end
|
|
32
32
|
|
data/lib/docscribe/infer.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative 'infer/literals'
|
|
|
16
16
|
require_relative 'infer/params'
|
|
17
17
|
require_relative 'infer/returns'
|
|
18
18
|
require_relative 'infer/raises'
|
|
19
|
+
require_relative 'infer/behavior'
|
|
19
20
|
|
|
20
21
|
module Docscribe
|
|
21
22
|
# Best-effort inference utilities used to generate YARD tags.
|
|
@@ -37,6 +38,40 @@ module Docscribe
|
|
|
37
38
|
Raises.infer_raises_from_node(node)
|
|
38
39
|
end
|
|
39
40
|
|
|
41
|
+
# Analyze method behavior from AST node and method name.
|
|
42
|
+
#
|
|
43
|
+
# @param [Object] node def or defs node
|
|
44
|
+
# @param [Object] method_name
|
|
45
|
+
# @return [Hash<Symbol, Object>]
|
|
46
|
+
def analyze_behavior(node, method_name)
|
|
47
|
+
body = extract_body(node)
|
|
48
|
+
Behavior.analyze(body, method_name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Get behavioral description for a method.
|
|
52
|
+
#
|
|
53
|
+
# @param [Object] node def or defs node
|
|
54
|
+
# @param [Object] method_name
|
|
55
|
+
# @return [String?]
|
|
56
|
+
def infer_behavior_description(node, method_name)
|
|
57
|
+
body = extract_body(node)
|
|
58
|
+
return nil unless body
|
|
59
|
+
|
|
60
|
+
analysis = Behavior.analyze(body, method_name)
|
|
61
|
+
Behavior.infer_description(analysis, method_name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Extract method body from def/defs node.
|
|
65
|
+
#
|
|
66
|
+
# @param [Object] node def or defs node
|
|
67
|
+
# @return [Object?]
|
|
68
|
+
def extract_body(node)
|
|
69
|
+
return nil unless node.is_a?(Parser::AST::Node)
|
|
70
|
+
|
|
71
|
+
body_idx = node.type == :def ? 2 : 3
|
|
72
|
+
node.children[body_idx]
|
|
73
|
+
end
|
|
74
|
+
|
|
40
75
|
# Infer a parameter type from its internal name form and optional default
|
|
41
76
|
# expression.
|
|
42
77
|
#
|
|
@@ -186,7 +186,7 @@ module Docscribe
|
|
|
186
186
|
# @param [Hash<Symbol, Object>] opts additional options including
|
|
187
187
|
# @return [Hash<Symbol, Object>]
|
|
188
188
|
def resolve_doc_setup!(setup, node, name, config, opts)
|
|
189
|
-
external_sig = resolve_external_sig(setup[:container], setup[:scope], name, opts[:signature_provider])
|
|
189
|
+
external_sig = resolve_external_sig(setup[:container], setup[:scope], name, opts[:signature_provider], node)
|
|
190
190
|
returns_spec = compute_returns_spec(node, config, opts[:param_types], opts[:core_rbs_provider],
|
|
191
191
|
signature_provider: opts[:signature_provider],
|
|
192
192
|
container: setup[:container])
|
|
@@ -219,9 +219,26 @@ module Docscribe
|
|
|
219
219
|
# @param [Symbol] scope method scope symbol
|
|
220
220
|
# @param [Symbol] name the method name string
|
|
221
221
|
# @param [Docscribe::Types::ProviderChain, nil] signature_provider external sig provider
|
|
222
|
+
# @param [Parser::AST::Node] node
|
|
222
223
|
# @return [Docscribe::Types::MethodSignature, nil]
|
|
223
|
-
def resolve_external_sig(container, scope, name, signature_provider)
|
|
224
|
-
|
|
224
|
+
def resolve_external_sig(container, scope, name, signature_provider, node = nil)
|
|
225
|
+
param_count, param_names = extract_sig_param_info(node)
|
|
226
|
+
signature_provider&.signature_for(container: container, scope: scope, name: name,
|
|
227
|
+
param_count: param_count, param_names: param_names)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# @note module_function: defines #extract_sig_param_info (visibility: private)
|
|
231
|
+
# @param [Parser::AST::Node?] node
|
|
232
|
+
# @return [(Integer?, Array<String>)]
|
|
233
|
+
def extract_sig_param_info(node)
|
|
234
|
+
empty = [] #: Array[String]
|
|
235
|
+
return [nil, empty] unless node
|
|
236
|
+
|
|
237
|
+
args = extract_args_from_node(node)
|
|
238
|
+
empty = [] #: Array[String]
|
|
239
|
+
return [nil, empty] unless args
|
|
240
|
+
|
|
241
|
+
[args.children.length, args.children.map { |a| a.children.first.to_s if a.respond_to?(:children) }.compact]
|
|
225
242
|
end
|
|
226
243
|
|
|
227
244
|
# Compute returns spec
|
|
@@ -594,7 +611,7 @@ module Docscribe
|
|
|
594
611
|
|
|
595
612
|
return_type = rest[1...type_end] #: String
|
|
596
613
|
desc = rest[(type_end + 1)..]&.strip
|
|
597
|
-
[return_type, desc
|
|
614
|
+
[return_type, desc && desc.empty? ? nil : desc]
|
|
598
615
|
end
|
|
599
616
|
|
|
600
617
|
# Extract all comment tags from line
|
|
@@ -832,9 +849,7 @@ module Docscribe
|
|
|
832
849
|
# @param [Hash<Symbol, Object>] info parse info hash to update with visibility flags
|
|
833
850
|
# @return [Array<String>]
|
|
834
851
|
def merge_module_function_note_lines(indent, insertion, name, info)
|
|
835
|
-
unless insertion.respond_to?(:module_function) && insertion.module_function && !info[:has_module_function_note]
|
|
836
|
-
return []
|
|
837
|
-
end
|
|
852
|
+
return [] unless insertion.respond_to?(:module_function) && insertion.module_function && !info[:has_module_function_note]
|
|
838
853
|
|
|
839
854
|
included_vis = insertion.included_instance_visibility || :private
|
|
840
855
|
["#{indent}# @note module_function: defines ##{name} (visibility: #{included_vis})"]
|
|
@@ -1228,9 +1243,7 @@ module Docscribe
|
|
|
1228
1243
|
end
|
|
1229
1244
|
|
|
1230
1245
|
method_name = :"build_#{arg_node.type}_lines"
|
|
1231
|
-
if respond_to?(method_name, true)
|
|
1232
|
-
return send(method_name, arg_node, indent, external_sig, param_types_override, **opts)
|
|
1233
|
-
end
|
|
1246
|
+
return send(method_name, arg_node, indent, external_sig, param_types_override, **opts) if respond_to?(method_name, true)
|
|
1234
1247
|
|
|
1235
1248
|
[]
|
|
1236
1249
|
end
|
|
@@ -158,7 +158,8 @@ module Docscribe
|
|
|
158
158
|
# @param [String] code the Ruby source code string to parse and rewrite
|
|
159
159
|
# @param [Hash<Symbol, Object>] options hash containing :config, :file, and :core_rbs_provider
|
|
160
160
|
# @raise [Docscribe::ParseError]
|
|
161
|
-
# @return [Hash<Symbol, Docscribe::Config, String, Parser::Source::Buffer, Parser::AST::Node, Docscribe::Types::ProviderChain, nil, Object, nil>]
|
|
161
|
+
# @return [Hash<Symbol, Docscribe::Config, String, Parser::Source::Buffer, Parser::AST::Node, Docscribe::Types::ProviderChain, nil, Object, nil>] rewrite environment with keys
|
|
162
|
+
# :config, :file, :buffer, :ast, :core_rbs_provider
|
|
162
163
|
def setup_rewrite_env(code, options)
|
|
163
164
|
config = options[:config] || Docscribe::Config.load
|
|
164
165
|
file = (options[:file] || '(inline)').to_s
|
|
@@ -454,7 +455,8 @@ module Docscribe
|
|
|
454
455
|
# Plugin insertion priority
|
|
455
456
|
#
|
|
456
457
|
# @private
|
|
457
|
-
# @param [Hash<Symbol, Object>, Docscribe::InlineRewriter::Collector::Insertion, Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected
|
|
458
|
+
# @param [Hash<Symbol, Object>, Docscribe::InlineRewriter::Collector::Insertion, Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected
|
|
459
|
+
# method insertion (plugin hash or collected insertion object)
|
|
458
460
|
# @raise [StandardError]
|
|
459
461
|
# @return [Integer]
|
|
460
462
|
# @return [Integer] if StandardError
|
|
@@ -469,7 +471,8 @@ module Docscribe
|
|
|
469
471
|
# Plugin insertion label
|
|
470
472
|
#
|
|
471
473
|
# @private
|
|
472
|
-
# @param [Hash<Symbol, Object>, Docscribe::InlineRewriter::Collector::Insertion, Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected
|
|
474
|
+
# @param [Hash<Symbol, Object>, Docscribe::InlineRewriter::Collector::Insertion, Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected
|
|
475
|
+
# method insertion (plugin hash or collected insertion object)
|
|
473
476
|
# @raise [StandardError]
|
|
474
477
|
# @return [String]
|
|
475
478
|
# @return [String] if StandardError
|
|
@@ -485,7 +488,8 @@ module Docscribe
|
|
|
485
488
|
# Plugin insertion line
|
|
486
489
|
#
|
|
487
490
|
# @private
|
|
488
|
-
# @param [Hash<Symbol, Object>, Docscribe::InlineRewriter::Collector::Insertion, Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected
|
|
491
|
+
# @param [Hash<Symbol, Object>, Docscribe::InlineRewriter::Collector::Insertion, Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected
|
|
492
|
+
# method insertion (plugin hash or collected insertion object)
|
|
489
493
|
# @raise [StandardError]
|
|
490
494
|
# @return [Integer, nil]
|
|
491
495
|
# @return [nil] if StandardError
|
|
@@ -630,9 +634,7 @@ module Docscribe
|
|
|
630
634
|
def normalize_plugin_doc(doc, indent, config:, anchor_node:)
|
|
631
635
|
doc = normalize_plugin_doc_indent(doc, indent)
|
|
632
636
|
doc = trim_trailing_blank_lines(doc)
|
|
633
|
-
if anchor_node && %i[def defs].include?(anchor_node.type) && config.include_default_message?
|
|
634
|
-
doc = prepend_default_message_if_no_prose(doc, anchor_node, indent, config)
|
|
635
|
-
end
|
|
637
|
+
doc = prepend_default_message_if_no_prose(doc, anchor_node, indent, config) if anchor_node && %i[def defs].include?(anchor_node.type) && config.include_default_message?
|
|
636
638
|
doc
|
|
637
639
|
end
|
|
638
640
|
|
|
@@ -794,9 +796,7 @@ module Docscribe
|
|
|
794
796
|
# @return [void]
|
|
795
797
|
def merge_existing_descriptions!(params, parsed)
|
|
796
798
|
params[:param_descriptions] = parsed[:param_descriptions] if parsed[:param_descriptions].any?
|
|
797
|
-
if parsed[:return_description] && !parsed[:return_description].start_with?('if ')
|
|
798
|
-
params[:return_description] = parsed[:return_description]
|
|
799
|
-
end
|
|
799
|
+
params[:return_description] = parsed[:return_description] if parsed[:return_description] && !parsed[:return_description].start_with?('if ')
|
|
800
800
|
params[:description] = parsed[:description] if parsed[:description].any?
|
|
801
801
|
end
|
|
802
802
|
|
|
@@ -841,13 +841,27 @@ module Docscribe
|
|
|
841
841
|
# @return [Docscribe::Types::MethodSignature, nil]
|
|
842
842
|
def resolve_external_signature(insertion, signature_provider)
|
|
843
843
|
node_name = SourceHelpers.node_name(insertion.node) #: Symbol
|
|
844
|
+
param_count, param_names = extract_param_info(insertion)
|
|
844
845
|
signature_provider&.signature_for(
|
|
845
846
|
container: insertion.container,
|
|
846
847
|
scope: insertion.scope,
|
|
847
|
-
name: node_name
|
|
848
|
+
name: node_name,
|
|
849
|
+
param_count: param_count,
|
|
850
|
+
param_names: param_names
|
|
848
851
|
)
|
|
849
852
|
end
|
|
850
853
|
|
|
854
|
+
# @private
|
|
855
|
+
# @param [Object] insertion
|
|
856
|
+
# @return [(Integer?, Array<Object>)]
|
|
857
|
+
def extract_param_info(insertion)
|
|
858
|
+
args = DocBuilder.extract_args_from_node(insertion.node)
|
|
859
|
+
empty = [] #: Array[untyped]
|
|
860
|
+
return [nil, empty] unless args
|
|
861
|
+
|
|
862
|
+
[args.children.length, args.children.map { |a| a.children.first.to_s if a.respond_to?(:children) }.compact]
|
|
863
|
+
end
|
|
864
|
+
|
|
851
865
|
# Resolve param types
|
|
852
866
|
#
|
|
853
867
|
# @private
|
|
@@ -1079,7 +1093,8 @@ module Docscribe
|
|
|
1079
1093
|
# @param [Docscribe::Config] config the active Docscribe::Config
|
|
1080
1094
|
# @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
|
|
1081
1095
|
# @param [Parser::Source::Range] bol_range the beginning-of-line range for the attribute node
|
|
1082
|
-
# @return [Hash<Symbol, Docscribe::InlineRewriter::Collector::AttrInsertion, Docscribe::Config, Docscribe::Types::ProviderChain, nil, Parser::Source::Range>]
|
|
1096
|
+
# @return [Hash<Symbol, Docscribe::InlineRewriter::Collector::AttrInsertion, Docscribe::Config, Docscribe::Types::ProviderChain, nil, Parser::Source::Range>] attr insertion params with keys
|
|
1097
|
+
# :insertion, :config, :signature_provider, :bol_range
|
|
1083
1098
|
def attr_insertion_params(insertion, config, signature_provider, bol_range)
|
|
1084
1099
|
{
|
|
1085
1100
|
insertion: insertion, config: config,
|
|
@@ -1237,19 +1252,52 @@ module Docscribe
|
|
|
1237
1252
|
# @return [String, nil]
|
|
1238
1253
|
# @return [nil] if StandardError
|
|
1239
1254
|
def build_attr_merge_additions(ins:, existing_lines:, config:, signature_provider:)
|
|
1240
|
-
missing = missing_attr_names(ins, existing_lines)
|
|
1241
|
-
return '' if missing.empty?
|
|
1242
|
-
|
|
1243
1255
|
indent = SourceHelpers.line_indent(ins.node)
|
|
1244
|
-
lines =
|
|
1245
|
-
lines
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
lines.map { |l| "#{l}\n" }.join
|
|
1256
|
+
lines = build_attr_merge_missing_lines(ins, existing_lines, indent, config, signature_provider)
|
|
1257
|
+
lines.concat(build_attr_merge_tag_lines(ins, existing_lines, indent, config, signature_provider))
|
|
1258
|
+
|
|
1259
|
+
lines.empty? ? '' : lines.map { |l| "#{l}\n" }.join
|
|
1249
1260
|
rescue StandardError
|
|
1250
1261
|
nil
|
|
1251
1262
|
end
|
|
1252
1263
|
|
|
1264
|
+
# @private
|
|
1265
|
+
# @param [Object] ins
|
|
1266
|
+
# @param [Array<String>] existing_lines
|
|
1267
|
+
# @param [String] indent
|
|
1268
|
+
# @param [Docscribe::Config] config
|
|
1269
|
+
# @param [Docscribe::Types::ProviderChain, nil] signature_provider
|
|
1270
|
+
# @return [Array<String>]
|
|
1271
|
+
def build_attr_merge_missing_lines(ins, existing_lines, indent, config, signature_provider)
|
|
1272
|
+
missing = missing_attr_names(ins, existing_lines)
|
|
1273
|
+
lines = [] #: Array[String]
|
|
1274
|
+
|
|
1275
|
+
unless missing.empty?
|
|
1276
|
+
lines << "#{indent}#" if existing_lines.any? && existing_lines.last.strip != '#'
|
|
1277
|
+
lines.concat(build_attr_doc_lines(ins, indent: indent, config: config,
|
|
1278
|
+
signature_provider: signature_provider, names: missing))
|
|
1279
|
+
end
|
|
1280
|
+
|
|
1281
|
+
lines
|
|
1282
|
+
end
|
|
1283
|
+
|
|
1284
|
+
# @private
|
|
1285
|
+
# @param [Object] ins
|
|
1286
|
+
# @param [Array<String>] existing_lines
|
|
1287
|
+
# @param [String] indent
|
|
1288
|
+
# @param [Docscribe::Config] config
|
|
1289
|
+
# @param [Docscribe::Types::ProviderChain, nil] signature_provider
|
|
1290
|
+
# @return [Array<String>]
|
|
1291
|
+
def build_attr_merge_tag_lines(ins, existing_lines, indent, config, signature_provider)
|
|
1292
|
+
tag_additions = build_missing_tag_additions(ins, existing_lines, indent, config, signature_provider)
|
|
1293
|
+
return [] if tag_additions.empty?
|
|
1294
|
+
|
|
1295
|
+
result = [] #: Array[String]
|
|
1296
|
+
result << "#{indent}#" if existing_lines.any? && existing_lines.last.strip != '#'
|
|
1297
|
+
result.concat(tag_additions)
|
|
1298
|
+
result
|
|
1299
|
+
end
|
|
1300
|
+
|
|
1253
1301
|
# Missing attr names
|
|
1254
1302
|
#
|
|
1255
1303
|
# @private
|
|
@@ -1278,6 +1326,80 @@ module Docscribe
|
|
|
1278
1326
|
names
|
|
1279
1327
|
end
|
|
1280
1328
|
|
|
1329
|
+
# Build missing tag additions for existing @!attribute lines
|
|
1330
|
+
#
|
|
1331
|
+
# @private
|
|
1332
|
+
# @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
|
|
1333
|
+
# @param [Array<String>] existing_lines array of existing doc comment lines
|
|
1334
|
+
# @param [String] indent whitespace indentation prefix
|
|
1335
|
+
# @param [Docscribe::Config] config the active Docscribe::Config
|
|
1336
|
+
# @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
|
|
1337
|
+
# @return [Array<String>]
|
|
1338
|
+
def build_missing_tag_additions(ins, existing_lines, indent, config, signature_provider)
|
|
1339
|
+
ins.names.each_with_object([]) do |name_sym, additions|
|
|
1340
|
+
idx = existing_attr_line_index(existing_lines, name_sym.to_s)
|
|
1341
|
+
next unless idx
|
|
1342
|
+
|
|
1343
|
+
attr_type = attribute_type(ins, name_sym, config, signature_provider: signature_provider)
|
|
1344
|
+
additions << "#{indent}# @return [#{attr_type}]" if attr_return_missing?(ins.access, existing_lines, idx)
|
|
1345
|
+
additions << format_attribute_param_tag(indent, 'value', attr_type, style: config.param_tag_style) if attr_param_missing?(ins.access, existing_lines, idx)
|
|
1346
|
+
end
|
|
1347
|
+
end
|
|
1348
|
+
|
|
1349
|
+
# @private
|
|
1350
|
+
# @param [Symbol] access
|
|
1351
|
+
# @param [Array<String>] existing_lines
|
|
1352
|
+
# @param [Integer] idx
|
|
1353
|
+
# @return [Boolean]
|
|
1354
|
+
def attr_return_missing?(access, existing_lines, idx)
|
|
1355
|
+
%i[r rw].include?(access) && !existing_lines_contain_tag?(existing_lines, idx, 'return')
|
|
1356
|
+
end
|
|
1357
|
+
|
|
1358
|
+
# @private
|
|
1359
|
+
# @param [Symbol] access
|
|
1360
|
+
# @param [Array<String>] existing_lines
|
|
1361
|
+
# @param [Integer] idx
|
|
1362
|
+
# @return [Boolean]
|
|
1363
|
+
def attr_param_missing?(access, existing_lines, idx)
|
|
1364
|
+
%i[w rw].include?(access) && !existing_lines_contain_tag?(existing_lines, idx, 'param')
|
|
1365
|
+
end
|
|
1366
|
+
|
|
1367
|
+
# Find index of line containing @!attribute for a specific name
|
|
1368
|
+
#
|
|
1369
|
+
# @private
|
|
1370
|
+
# @param [Array<String>] lines array of existing doc comment lines
|
|
1371
|
+
# @param [String] name the attribute name to search for
|
|
1372
|
+
# @return [Integer?]
|
|
1373
|
+
def existing_attr_line_index(lines, name)
|
|
1374
|
+
Array(lines).each_with_index do |line, idx|
|
|
1375
|
+
return idx if line.match?(/\A\s*#\s*@!attribute\b(?:\s+\[[^\]]+\])?\s+#{Regexp.escape(name)}\b/)
|
|
1376
|
+
end
|
|
1377
|
+
nil
|
|
1378
|
+
end
|
|
1379
|
+
|
|
1380
|
+
# Check if lines after an @!attribute line contain a specific tag
|
|
1381
|
+
#
|
|
1382
|
+
# Walks forward from attr_line_idx + 1, stopping at the next top-level
|
|
1383
|
+
# directive or non-comment line.
|
|
1384
|
+
#
|
|
1385
|
+
# @private
|
|
1386
|
+
# @param [Array<String>] lines array of existing doc comment lines
|
|
1387
|
+
# @param [Integer] attr_line_idx index of the @!attribute line
|
|
1388
|
+
# @param [String] tag_name the tag name to search for (e.g. "return", "param")
|
|
1389
|
+
# @return [Boolean]
|
|
1390
|
+
def existing_lines_contain_tag?(lines, attr_line_idx, tag_name)
|
|
1391
|
+
i = attr_line_idx + 1
|
|
1392
|
+
while i < lines.length
|
|
1393
|
+
line = lines[i]
|
|
1394
|
+
break unless line.match?(/\A\s*#/) # not a comment line
|
|
1395
|
+
break if line.match?(/\A\s*# @(?!@)/) # next top-level directive (single space)
|
|
1396
|
+
return true if line.match?(/\A\s*#\s{2,}@#{tag_name}\b/)
|
|
1397
|
+
|
|
1398
|
+
i += 1
|
|
1399
|
+
end
|
|
1400
|
+
false
|
|
1401
|
+
end
|
|
1402
|
+
|
|
1281
1403
|
# Attribute allowed
|
|
1282
1404
|
#
|
|
1283
1405
|
# @private
|