docscribe 1.5.2 → 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 +155 -88
- data/exe/docscribe-client +3 -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 +2 -1
- data/lib/docscribe/cli/run.rb +134 -0
- 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 +1 -3
- data/lib/docscribe/infer.rb +35 -0
- data/lib/docscribe/inline_rewriter/doc_builder.rb +22 -9
- data/lib/docscribe/inline_rewriter.rb +142 -20
- data/lib/docscribe/server.rb +3 -2
- 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 +6 -2
|
@@ -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)
|
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
|
|
@@ -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
|
data/lib/docscribe/server.rb
CHANGED
|
@@ -651,7 +651,8 @@ module Docscribe
|
|
|
651
651
|
def rewrite_and_cache(file, strategy, config, key, mtime)
|
|
652
652
|
src = File.read(file)
|
|
653
653
|
rbs = config.respond_to?(:core_rbs_provider) ? config.core_rbs_provider : nil
|
|
654
|
-
result = Docscribe::InlineRewriter.rewrite_with_report(src, strategy: strategy, config: config,
|
|
654
|
+
result = Docscribe::InlineRewriter.rewrite_with_report(src, strategy: strategy, config: config,
|
|
655
|
+
core_rbs_provider: rbs, file: file)
|
|
655
656
|
@file_cache[key] = { mtime: mtime, src: src, result: result }
|
|
656
657
|
[src, result]
|
|
657
658
|
end
|
|
@@ -689,7 +690,7 @@ module Docscribe
|
|
|
689
690
|
# @private
|
|
690
691
|
# @param [UNIXSocket] client connected client socket
|
|
691
692
|
# @param [String, Integer] id request ID
|
|
692
|
-
# @param [Object] result result data
|
|
693
|
+
# @param [Hash<String, Object>] result result data
|
|
693
694
|
# @return [void]
|
|
694
695
|
def send_result(client, id, result)
|
|
695
696
|
response = { jsonrpc: '2.0', id: id, result: result }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Types
|
|
5
|
+
# Selects best matching overload from RBS signatures for given arguments.
|
|
6
|
+
module OverloadSelector
|
|
7
|
+
class << self
|
|
8
|
+
# @param [Array<Object>] overloads
|
|
9
|
+
# @param [Integer] arg_count
|
|
10
|
+
# @param [Array<String>] param_names
|
|
11
|
+
# @return [Object?]
|
|
12
|
+
def select(overloads, arg_count:, param_names: [])
|
|
13
|
+
return nil if overloads.nil? || overloads.empty?
|
|
14
|
+
return overloads.first if overloads.size == 1
|
|
15
|
+
|
|
16
|
+
best = best_match(overloads, arg_count, param_names)
|
|
17
|
+
best&.first || overloads.first
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param [Array<Object>] overloads
|
|
21
|
+
# @param [Integer] arg_count
|
|
22
|
+
# @param [Array<String>] param_names
|
|
23
|
+
# @return [(Object, Integer)?]
|
|
24
|
+
def best_match(overloads, arg_count, param_names)
|
|
25
|
+
candidates = overloads.map { |sig| score_signature(sig, arg_count: arg_count, param_names: param_names) }
|
|
26
|
+
candidates.compact.max_by { |_sig, score| score }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# @private
|
|
32
|
+
# @param [Object] sig
|
|
33
|
+
# @param [Integer] arg_count
|
|
34
|
+
# @param [Array<String>] param_names
|
|
35
|
+
# @return [(Object, Integer)?]
|
|
36
|
+
def score_signature(sig, arg_count:, param_names:)
|
|
37
|
+
score = 0
|
|
38
|
+
|
|
39
|
+
pos_count = sig.positional_types&.length.to_i
|
|
40
|
+
return nil if pos_count > arg_count
|
|
41
|
+
|
|
42
|
+
score += score_positional(pos_count, arg_count, sig)
|
|
43
|
+
score += score_params(sig, param_names)
|
|
44
|
+
score += 3 if sig.return_type && !sig.return_type.empty?
|
|
45
|
+
score += 1 if sig.return_type && sig.return_type != 'Object'
|
|
46
|
+
|
|
47
|
+
[sig, score]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @private
|
|
51
|
+
# @param [Integer] pos_count
|
|
52
|
+
# @param [Integer] arg_count
|
|
53
|
+
# @param [Object] sig
|
|
54
|
+
# @return [Integer]
|
|
55
|
+
def score_positional(pos_count, arg_count, sig)
|
|
56
|
+
if pos_count == arg_count
|
|
57
|
+
10
|
|
58
|
+
elsif pos_count < arg_count && sig.rest_positional
|
|
59
|
+
5
|
|
60
|
+
else
|
|
61
|
+
0
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @private
|
|
66
|
+
# @param [Object] sig
|
|
67
|
+
# @param [Array<String>] param_names
|
|
68
|
+
# @return [Integer]
|
|
69
|
+
def score_params(sig, param_names)
|
|
70
|
+
if sig.param_types
|
|
71
|
+
(sig.param_types.keys & param_names).length * 2
|
|
72
|
+
else
|
|
73
|
+
0
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'overload_selector'
|
|
4
|
+
|
|
3
5
|
module Docscribe
|
|
4
6
|
module Types
|
|
5
7
|
# Resolve method signatures by querying a list of providers in order.
|
|
@@ -22,18 +24,39 @@ module Docscribe
|
|
|
22
24
|
|
|
23
25
|
# Resolve a method signature from the first provider that can supply it.
|
|
24
26
|
#
|
|
27
|
+
# When overloads are present, selects the best-matching signature.
|
|
28
|
+
#
|
|
25
29
|
# @param [String] container e.g. "MyModule::MyClass"
|
|
26
30
|
# @param [Symbol] scope :instance or :class
|
|
27
31
|
# @param [Symbol, String] name method name
|
|
32
|
+
# @param [Integer?] param_count number of actual arguments
|
|
33
|
+
# @param [Array<String>] param_names actual parameter names
|
|
28
34
|
# @return [Docscribe::Types::MethodSignature, nil]
|
|
29
|
-
def signature_for(container:, scope:, name:)
|
|
35
|
+
def signature_for(container:, scope:, name:, param_count: nil, param_names: [])
|
|
30
36
|
@providers.each do |provider|
|
|
31
37
|
sig = provider.signature_for(container: container, scope: scope, name: name)
|
|
32
|
-
|
|
38
|
+
next unless sig
|
|
39
|
+
|
|
40
|
+
return sig unless sig.overloads&.any?
|
|
41
|
+
|
|
42
|
+
best = select_overload(sig, param_count, param_names)
|
|
43
|
+
return best if best
|
|
33
44
|
end
|
|
34
45
|
|
|
35
46
|
nil
|
|
36
47
|
end
|
|
48
|
+
|
|
49
|
+
# @param [Docscribe::Types::MethodSignature] sig
|
|
50
|
+
# @param [Integer?] param_count
|
|
51
|
+
# @param [Array<String>] param_names
|
|
52
|
+
# @return [Docscribe::Types::MethodSignature, nil]
|
|
53
|
+
def select_overload(sig, param_count, param_names)
|
|
54
|
+
OverloadSelector.select(
|
|
55
|
+
[sig, *sig.overloads],
|
|
56
|
+
arg_count: param_count || 0,
|
|
57
|
+
param_names: param_names
|
|
58
|
+
)
|
|
59
|
+
end
|
|
37
60
|
end
|
|
38
61
|
end
|
|
39
62
|
end
|
|
@@ -21,7 +21,12 @@ module Docscribe
|
|
|
21
21
|
# @!attribute [rw] rest_keywords
|
|
22
22
|
# @return [Docscribe::Types::RestKeywords, nil]
|
|
23
23
|
# @param [Docscribe::Types::RestKeywords, nil] value
|
|
24
|
+
#
|
|
25
|
+
# @!attribute [rw] overloads
|
|
26
|
+
# @return [Array<Docscribe::Types::MethodSignature>, nil]
|
|
27
|
+
# @param [Array<Docscribe::Types::MethodSignature>, nil] value
|
|
24
28
|
MethodSignature = Struct.new(:return_type, :param_types, :positional_types, :rest_positional, :rest_keywords,
|
|
29
|
+
:overloads,
|
|
25
30
|
keyword_init: true)
|
|
26
31
|
|
|
27
32
|
# @!attribute [rw] name
|
data/lib/docscribe/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: docscribe
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- unurgunite
|
|
@@ -163,6 +163,8 @@ files:
|
|
|
163
163
|
- lib/docscribe/cli.rb
|
|
164
164
|
- lib/docscribe/cli/check_for_comments.rb
|
|
165
165
|
- lib/docscribe/cli/config_builder.rb
|
|
166
|
+
- lib/docscribe/cli/config_dump.rb
|
|
167
|
+
- lib/docscribe/cli/coverage.rb
|
|
166
168
|
- lib/docscribe/cli/formatters.rb
|
|
167
169
|
- lib/docscribe/cli/formatters/json.rb
|
|
168
170
|
- lib/docscribe/cli/formatters/sarif.rb
|
|
@@ -188,6 +190,7 @@ files:
|
|
|
188
190
|
- lib/docscribe/config/utils.rb
|
|
189
191
|
- lib/docscribe/infer.rb
|
|
190
192
|
- lib/docscribe/infer/ast_walk.rb
|
|
193
|
+
- lib/docscribe/infer/behavior.rb
|
|
191
194
|
- lib/docscribe/infer/constants.rb
|
|
192
195
|
- lib/docscribe/infer/literals.rb
|
|
193
196
|
- lib/docscribe/infer/names.rb
|
|
@@ -209,6 +212,7 @@ files:
|
|
|
209
212
|
- lib/docscribe/plugin/registry.rb
|
|
210
213
|
- lib/docscribe/plugin/tag.rb
|
|
211
214
|
- lib/docscribe/server.rb
|
|
215
|
+
- lib/docscribe/types/overload_selector.rb
|
|
212
216
|
- lib/docscribe/types/provider_chain.rb
|
|
213
217
|
- lib/docscribe/types/rbs/collection_loader.rb
|
|
214
218
|
- lib/docscribe/types/rbs/provider.rb
|
|
@@ -230,7 +234,7 @@ metadata:
|
|
|
230
234
|
changelog_uri: https://github.com/unurgunite/docscribe/blob/master/CHANGELOG.md
|
|
231
235
|
rubygems_mfa_required: 'true'
|
|
232
236
|
post_install_message: |
|
|
233
|
-
You installed docscribe 1.
|
|
237
|
+
You installed docscribe 1.6.0. Your future self (and your team) thank you.
|
|
234
238
|
|
|
235
239
|
$ docscribe --help
|
|
236
240
|
|