docscribe 1.4.1 → 1.5.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +588 -104
  3. data/lib/docscribe/cli/check_for_comments.rb +183 -0
  4. data/lib/docscribe/cli/config_builder.rb +180 -36
  5. data/lib/docscribe/cli/formatters/json.rb +294 -0
  6. data/lib/docscribe/cli/formatters/sarif.rb +235 -0
  7. data/lib/docscribe/cli/formatters/text.rb +208 -0
  8. data/lib/docscribe/cli/formatters.rb +26 -0
  9. data/lib/docscribe/cli/generate.rb +296 -125
  10. data/lib/docscribe/cli/init.rb +58 -14
  11. data/lib/docscribe/cli/options.rb +410 -133
  12. data/lib/docscribe/cli/rbs_gen.rb +529 -0
  13. data/lib/docscribe/cli/run.rb +503 -189
  14. data/lib/docscribe/cli/sigs.rb +366 -0
  15. data/lib/docscribe/cli/update_types.rb +103 -0
  16. data/lib/docscribe/cli.rb +35 -9
  17. data/lib/docscribe/config/defaults.rb +16 -12
  18. data/lib/docscribe/config/emit.rb +18 -0
  19. data/lib/docscribe/config/filtering.rb +37 -31
  20. data/lib/docscribe/config/loader.rb +20 -13
  21. data/lib/docscribe/config/plugin.rb +2 -1
  22. data/lib/docscribe/config/rbs.rb +68 -27
  23. data/lib/docscribe/config/sorbet.rb +40 -17
  24. data/lib/docscribe/config/sorting.rb +2 -1
  25. data/lib/docscribe/config/template.rb +10 -1
  26. data/lib/docscribe/config/utils.rb +12 -9
  27. data/lib/docscribe/config.rb +3 -4
  28. data/lib/docscribe/infer/ast_walk.rb +1 -1
  29. data/lib/docscribe/infer/constants.rb +15 -0
  30. data/lib/docscribe/infer/literals.rb +39 -26
  31. data/lib/docscribe/infer/names.rb +24 -16
  32. data/lib/docscribe/infer/params.rb +57 -13
  33. data/lib/docscribe/infer/raises.rb +23 -15
  34. data/lib/docscribe/infer/returns.rb +784 -199
  35. data/lib/docscribe/infer.rb +28 -28
  36. data/lib/docscribe/inline_rewriter/collector.rb +816 -430
  37. data/lib/docscribe/inline_rewriter/doc_block.rb +323 -150
  38. data/lib/docscribe/inline_rewriter/doc_builder.rb +1837 -648
  39. data/lib/docscribe/inline_rewriter/source_helpers.rb +119 -71
  40. data/lib/docscribe/inline_rewriter/tag_sorter.rb +165 -107
  41. data/lib/docscribe/inline_rewriter.rb +1144 -727
  42. data/lib/docscribe/parsing.rb +29 -10
  43. data/lib/docscribe/plugin/base/collector_plugin.rb +3 -3
  44. data/lib/docscribe/plugin/base/tag_plugin.rb +1 -2
  45. data/lib/docscribe/plugin/context.rb +28 -18
  46. data/lib/docscribe/plugin/registry.rb +49 -23
  47. data/lib/docscribe/plugin/tag.rb +9 -14
  48. data/lib/docscribe/plugin.rb +54 -22
  49. data/lib/docscribe/types/provider_chain.rb +4 -2
  50. data/lib/docscribe/types/rbs/collection_loader.rb +2 -3
  51. data/lib/docscribe/types/rbs/provider.rb +127 -62
  52. data/lib/docscribe/types/rbs/type_formatter.rb +286 -77
  53. data/lib/docscribe/types/signature.rb +22 -42
  54. data/lib/docscribe/types/sorbet/base_provider.rb +51 -27
  55. data/lib/docscribe/types/sorbet/rbi_provider.rb +3 -3
  56. data/lib/docscribe/types/sorbet/source_provider.rb +3 -2
  57. data/lib/docscribe/types/yard/formatter.rb +100 -0
  58. data/lib/docscribe/types/yard/parser.rb +240 -0
  59. data/lib/docscribe/types/yard/types.rb +52 -0
  60. data/lib/docscribe/version.rb +1 -1
  61. metadata +34 -2
@@ -17,38 +17,51 @@ module Docscribe
17
17
  #
18
18
  # If the node does not match a supported pattern, the fallback type is returned.
19
19
  #
20
- # @note module_function: when included, also defines #type_from_literal (instance visibility: private)
20
+ # @note module_function: defines #type_from_literal (visibility: private)
21
21
  # @param [Parser::AST::Node, nil] node literal/value node
22
22
  # @param [String] fallback_type type returned when inference is uncertain
23
23
  # @return [String]
24
24
  def type_from_literal(node, fallback_type: FALLBACK_TYPE)
25
25
  return fallback_type unless node
26
26
 
27
- case node.type
28
- when :int then 'Integer'
29
- when :float then 'Float'
30
- when :str, :dstr then 'String'
31
- when :sym then 'Symbol'
32
- when :true, :false then 'Boolean' # rubocop:disable Lint/BooleanSymbol
33
- when :nil then 'nil'
34
- when :array then 'Array'
35
- when :hash then 'Hash'
36
- when :regexp then 'Regexp'
37
-
38
- when :const
39
- node.children.last.to_s
40
-
41
- when :send
42
- recv, meth, = node.children
43
- if meth == :new && recv && recv.type == :const
44
- recv.children.last.to_s
45
- else
46
- fallback_type
47
- end
48
-
49
- else
50
- fallback_type
51
- end
27
+ literal_type_for(node.type) || const_type_for(node, fallback_type) ||
28
+ send_new_type_for(node, fallback_type) || fallback_type
29
+ end
30
+
31
+ # Map a node type symbol to a known literal type name.
32
+ #
33
+ # @note module_function: defines #literal_type_for (visibility: private)
34
+ # @param [Symbol] type node type
35
+ # @return [String, nil]
36
+ def literal_type_for(type)
37
+ LITERAL_TYPE_MAP[type]
38
+ end
39
+
40
+ # Extract a constant name from a `:const` node.
41
+ #
42
+ # @note module_function: defines #const_type_for (visibility: private)
43
+ # @param [Parser::AST::Node] node literal/value node
44
+ # @param [String] _fallback_type fallback type string (unused here)
45
+ # @return [String, nil]
46
+ def const_type_for(node, _fallback_type)
47
+ return unless node.type == :const
48
+
49
+ node.children.last.to_s
50
+ end
51
+
52
+ # Extract a type from a `Foo.new` send node.
53
+ #
54
+ # @note module_function: defines #send_new_type_for (visibility: private)
55
+ # @param [Parser::AST::Node] node literal/value node
56
+ # @param [String] _fallback_type fallback type string (unused here)
57
+ # @return [String, nil]
58
+ def send_new_type_for(node, _fallback_type)
59
+ return unless node.type == :send
60
+
61
+ recv, meth, = node.children
62
+ return unless meth == :new && recv&.type == :const
63
+
64
+ recv.children.last.to_s
52
65
  end
53
66
  end
54
67
  end
@@ -15,27 +15,35 @@ module Docscribe
15
15
  #
16
16
  # Returns nil for unsupported nodes.
17
17
  #
18
- # @note module_function: when included, also defines #const_full_name (instance visibility: private)
19
- # @param [Parser::AST::Node, nil] n constant-like AST node
18
+ # @note module_function: defines #const_full_name (visibility: private)
19
+ # @param [Parser::AST::Node, nil] node constant-like AST node
20
20
  # @return [String, nil]
21
- def const_full_name(n)
22
- return nil unless n.is_a?(Parser::AST::Node)
21
+ def const_full_name(node)
22
+ return nil unless node.is_a?(Parser::AST::Node)
23
23
 
24
- case n.type
24
+ case node.type
25
25
  when :const
26
- scope, name = *n
27
- scope_name = const_full_name(scope)
26
+ build_const_full_name(node)
27
+ when :cbase
28
+ ''
29
+ end
30
+ end
28
31
 
29
- if scope_name && !scope_name.empty?
30
- "#{scope_name}::#{name}"
31
- elsif scope_name == '' # leading ::
32
- "::#{name}"
33
- else
34
- name.to_s
35
- end
32
+ # Build the fully qualified name from a `:const` node.
33
+ #
34
+ # @note module_function: defines #build_const_full_name (visibility: private)
35
+ # @param [Parser::AST::Node] node a `:const` node
36
+ # @return [String]
37
+ def build_const_full_name(node)
38
+ scope, name = *node
39
+ scope_name = const_full_name(scope)
36
40
 
37
- when :cbase
38
- '' # represents leading :: scope
41
+ if scope_name && !scope_name.empty?
42
+ "#{scope_name}::#{name}"
43
+ elsif scope_name == ''
44
+ "::#{name}"
45
+ else
46
+ name.to_s
39
47
  end
40
48
  end
41
49
  end
@@ -15,46 +15,90 @@ module Docscribe
15
15
  # - special-casing `options:` as `Hash` when enabled
16
16
  # - literal defaults via AST parsing
17
17
  #
18
- # @note module_function: when included, also defines #infer_param_type (instance visibility: private)
18
+ # @note module_function: defines #infer_param_type (visibility: private)
19
19
  # @param [String] name parameter name as used internally (may include `*`, `**`, `&`, or trailing `:`)
20
- # @param [String, nil] default_str source for the default value expression
20
+ # @param [String?] default_str source for the default value expression
21
21
  # @param [String] fallback_type type returned when inference is uncertain
22
22
  # @param [Boolean] treat_options_keyword_as_hash whether `options:` should
23
- # be treated specially as Hash
24
23
  # @return [String]
25
24
  def infer_param_type(name, default_str, fallback_type: FALLBACK_TYPE, treat_options_keyword_as_hash: true)
25
+ prefix_param_type(name) || inferred_param_type(name, default_str, fallback_type,
26
+ treat_options_keyword_as_hash: treat_options_keyword_as_hash)
27
+ end
28
+
29
+ # Return type for special parameter prefixes.
30
+ #
31
+ # @note module_function: defines #prefix_param_type (visibility: private)
32
+ # @param [String] name parameter name
33
+ # @return [String, nil]
34
+ def prefix_param_type(name)
26
35
  return 'Array' if name.start_with?('*') && !name.start_with?('**')
27
36
  return 'Hash' if name.start_with?('**')
28
37
  return 'Proc' if name.start_with?('&')
29
38
 
30
- is_kw = name.end_with?(':')
31
- node = parse_expr(default_str)
32
- ty = Literals.type_from_literal(node, fallback_type: fallback_type)
39
+ nil
40
+ end
33
41
 
34
- if is_kw && default_str.nil?
35
- return (treat_options_keyword_as_hash && name == 'options:' ? 'Hash' : fallback_type)
42
+ # Infer type for a regular or keyword parameter with optional default.
43
+ #
44
+ # @note module_function: defines #inferred_param_type (visibility: private)
45
+ # @param [String] name parameter name
46
+ # @param [String?] default_str default expression source
47
+ # @param [String] fallback_type type returned when not special-cased
48
+ # @param [Boolean] treat_options_keyword_as_hash whether to treat 'options:' as Hash
49
+ # @return [String]
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)
36
53
  end
37
54
 
38
- return 'Hash' if treat_options_keyword_as_hash && name == 'options:' && (default_str == '{}' || ty == 'Hash')
55
+ node = parse_expr(default_str)
56
+ ty = Literals.type_from_literal(node, fallback_type: fallback_type)
57
+
58
+ return 'Hash' if options_hash_keyword?(name, default_str, ty, treat_options_keyword_as_hash)
39
59
 
40
60
  ty
41
61
  end
42
62
 
63
+ # Return 'Hash' for a keyword parameter named 'options:' when special-cased, else fallback.
64
+ #
65
+ # @note module_function: defines #options_keyword_type (visibility: private)
66
+ # @param [String] name parameter name
67
+ # @param [Boolean] treat_options_keyword_as_hash whether to treat 'options:' as Hash
68
+ # @param [String] fallback_type type returned when not special-cased
69
+ # @return [String]
70
+ def options_keyword_type(name, treat_options_keyword_as_hash, fallback_type)
71
+ treat_options_keyword_as_hash && name == 'options:' ? 'Hash' : fallback_type
72
+ end
73
+
74
+ # Whether a keyword parameter named 'options:' with a hash default should be typed as Hash.
75
+ #
76
+ # @note module_function: defines #options_hash_keyword? (visibility: private)
77
+ # @param [String] name parameter name
78
+ # @param [String?] default_str default expression source
79
+ # @param [String] type inferred type
80
+ # @param [Boolean] treat_options_keyword_as_hash whether to treat 'options:' as Hash
81
+ # @return [Boolean]
82
+ def options_hash_keyword?(name, default_str, type, treat_options_keyword_as_hash)
83
+ treat_options_keyword_as_hash && name == 'options:' && (default_str == '{}' || type == 'Hash')
84
+ end
85
+
43
86
  # Parse a standalone expression for parameter-default inference.
44
87
  #
45
88
  # Returns nil if the expression is empty or cannot be parsed.
46
89
  #
47
- # @note module_function: when included, also defines #parse_expr (instance visibility: private)
48
- # @param [String, nil] src expression source
90
+ # @note module_function: defines #parse_expr (visibility: private)
91
+ # @param [String?] src expression source
49
92
  # @raise [Parser::SyntaxError]
50
- # @return [Parser::AST::Node, nil]
93
+ # @return [Parser::AST::Node, nil] if Parser::SyntaxError
94
+ # @return [nil] if Parser::SyntaxError
51
95
  def parse_expr(src)
52
96
  return nil if src.nil? || src.strip.empty?
53
97
 
54
98
  buffer = Parser::Source::Buffer.new('(param)')
55
99
  buffer.source = src
56
100
  Docscribe::Parsing.parse_buffer(buffer)
57
- rescue Parser::SyntaxError
101
+ rescue Parser::SyntaxError # steep:ignore
58
102
  nil
59
103
  end
60
104
  end
@@ -16,28 +16,18 @@ module Docscribe
16
16
  #
17
17
  # Returns unique exception names in discovery order.
18
18
  #
19
- # @note module_function: when included, also defines #infer_raises_from_node (instance visibility: private)
19
+ # @note module_function: defines #infer_raises_from_node (visibility: private)
20
20
  # @param [Parser::AST::Node] node method or expression node to inspect
21
21
  # @return [Array<String>]
22
22
  def infer_raises_from_node(node)
23
- raises = []
23
+ raises = [] #: Array[String]
24
24
 
25
25
  ASTWalk.walk(node) do |n|
26
26
  case n.type
27
27
  when :resbody
28
- exc_list = n.children[0]
29
- raises.concat(exception_names_from_rescue_list(exc_list))
30
-
28
+ raises.concat(exception_names_from_rescue_list(n.children[0]))
31
29
  when :send
32
- recv, meth, *args = *n
33
- next unless recv.nil? && %i[raise fail].include?(meth)
34
-
35
- if args.empty?
36
- raises << DEFAULT_ERROR
37
- else
38
- c = Names.const_full_name(args[0])
39
- raises << (c || DEFAULT_ERROR)
40
- end
30
+ collect_send_raise(raises, n)
41
31
  end
42
32
  end
43
33
 
@@ -51,7 +41,7 @@ module Docscribe
51
41
  # - `Foo` => `["Foo"]`
52
42
  # - `[Foo, Bar]` => `["Foo", "Bar"]`
53
43
  #
54
- # @note module_function: when included, also defines #exception_names_from_rescue_list (instance visibility: private)
44
+ # @note module_function: defines #exception_names_from_rescue_list (visibility: private)
55
45
  # @param [Parser::AST::Node, nil] exc_list rescue exception list node
56
46
  # @return [Array<String>]
57
47
  def exception_names_from_rescue_list(exc_list)
@@ -63,6 +53,24 @@ module Docscribe
63
53
  [Names.const_full_name(exc_list) || DEFAULT_ERROR]
64
54
  end
65
55
  end
56
+
57
+ # Collect exception names from a `raise` or `fail` send node.
58
+ #
59
+ # @note module_function: defines #collect_send_raise (visibility: private)
60
+ # @param [Array<String>] raises accumulator
61
+ # @param [Parser::AST::Node] node send node
62
+ # @return [void]
63
+ def collect_send_raise(raises, node)
64
+ recv, meth, *args = *node
65
+ return unless recv.nil? && %i[raise fail].include?(meth)
66
+
67
+ if args.empty?
68
+ raises << DEFAULT_ERROR
69
+ else
70
+ c = Names.const_full_name(args[0])
71
+ raises << (c || DEFAULT_ERROR)
72
+ end
73
+ end
66
74
  end
67
75
  end
68
76
  end