docscribe 1.4.2 → 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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +465 -130
  3. data/lib/docscribe/cli/check_for_comments.rb +183 -0
  4. data/lib/docscribe/cli/config_builder.rb +107 -53
  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 +45 -45
  10. data/lib/docscribe/cli/init.rb +14 -6
  11. data/lib/docscribe/cli/options.rb +190 -88
  12. data/lib/docscribe/cli/rbs_gen.rb +529 -0
  13. data/lib/docscribe/cli/run.rb +210 -152
  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 +21 -13
  17. data/lib/docscribe/config/defaults.rb +5 -1
  18. data/lib/docscribe/config/emit.rb +17 -0
  19. data/lib/docscribe/config/filtering.rb +18 -25
  20. data/lib/docscribe/config/loader.rb +15 -11
  21. data/lib/docscribe/config/plugin.rb +1 -1
  22. data/lib/docscribe/config/rbs.rb +41 -9
  23. data/lib/docscribe/config/sorbet.rb +9 -12
  24. data/lib/docscribe/config/sorting.rb +1 -1
  25. data/lib/docscribe/config/template.rb +9 -1
  26. data/lib/docscribe/config/utils.rb +11 -9
  27. data/lib/docscribe/config.rb +2 -4
  28. data/lib/docscribe/infer/ast_walk.rb +1 -1
  29. data/lib/docscribe/infer/literals.rb +6 -11
  30. data/lib/docscribe/infer/names.rb +2 -3
  31. data/lib/docscribe/infer/params.rb +15 -17
  32. data/lib/docscribe/infer/raises.rb +3 -5
  33. data/lib/docscribe/infer/returns.rb +542 -140
  34. data/lib/docscribe/infer.rb +22 -23
  35. data/lib/docscribe/inline_rewriter/collector.rb +159 -164
  36. data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
  37. data/lib/docscribe/inline_rewriter/doc_builder.rb +1026 -723
  38. data/lib/docscribe/inline_rewriter/source_helpers.rb +49 -49
  39. data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
  40. data/lib/docscribe/inline_rewriter.rb +495 -492
  41. data/lib/docscribe/parsing.rb +29 -10
  42. data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
  43. data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
  44. data/lib/docscribe/plugin/context.rb +28 -18
  45. data/lib/docscribe/plugin/registry.rb +26 -27
  46. data/lib/docscribe/plugin/tag.rb +9 -14
  47. data/lib/docscribe/plugin.rb +17 -16
  48. data/lib/docscribe/types/provider_chain.rb +4 -2
  49. data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
  50. data/lib/docscribe/types/rbs/provider.rb +60 -44
  51. data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
  52. data/lib/docscribe/types/signature.rb +22 -42
  53. data/lib/docscribe/types/sorbet/base_provider.rb +24 -19
  54. data/lib/docscribe/types/sorbet/rbi_provider.rb +3 -3
  55. data/lib/docscribe/types/sorbet/source_provider.rb +3 -2
  56. data/lib/docscribe/types/yard/formatter.rb +100 -0
  57. data/lib/docscribe/types/yard/parser.rb +240 -0
  58. data/lib/docscribe/types/yard/types.rb +52 -0
  59. data/lib/docscribe/version.rb +1 -1
  60. metadata +33 -1
@@ -36,20 +36,31 @@ module Docscribe
36
36
 
37
37
  # Parse a prepared source buffer into a parser-gem-compatible AST.
38
38
  #
39
- # @param [Parser::Source::Buffer] buffer
39
+ # Returns +nil+ when the source cannot be parsed (syntax errors or
40
+ # internal parser crashes).
41
+ #
42
+ # @param [Parser::Source::Buffer] buffer prepared source buffer
40
43
  # @param [Symbol] backend :auto, :parser, or :prism
41
- # @return [Parser::AST::Node, nil]
44
+ # @raise [NoMethodError]
45
+ # @return [Parser::AST::Node, nil] if NoMethodError
46
+ # @return [nil] if NoMethodError
42
47
  def parse_buffer(buffer, backend: :auto)
43
48
  parser = parser_for(backend: backend)
49
+ return nil unless parser
50
+
44
51
  parser.parse(buffer)
52
+ rescue NoMethodError
53
+ nil
45
54
  end
46
55
 
47
56
  # Parse source code and also return comments when supported by the backend.
48
57
  #
58
+ # Returns +nil+ when the source cannot be parsed.
59
+ #
49
60
  # @param [String] code Ruby source
50
61
  # @param [String] file source name used for parser locations
51
62
  # @param [Symbol] backend :auto, :parser, or :prism
52
- # @return [Array<(Parser::AST::Node, Array)>]
63
+ # @return [(Parser::AST::Node?, Array<Parser::Source::Comment>), nil]
53
64
  def parse_with_comments(code, file: '(docscribe)', backend: :auto)
54
65
  buffer = Parser::Source::Buffer.new(file, source: code)
55
66
  parse_with_comments_buffer(buffer, backend: backend)
@@ -57,12 +68,20 @@ module Docscribe
57
68
 
58
69
  # Parse a prepared source buffer and also return comments when supported by the backend.
59
70
  #
60
- # @param [Parser::Source::Buffer] buffer
71
+ # Returns +nil+ when the source cannot be parsed.
72
+ #
73
+ # @param [Parser::Source::Buffer] buffer prepared source buffer
61
74
  # @param [Symbol] backend :auto, :parser, or :prism
62
- # @return [Array<(Parser::AST::Node, Array)>]
75
+ # @raise [NoMethodError]
76
+ # @return [(Parser::AST::Node?, Array<Parser::Source::Comment>), nil] if NoMethodError
77
+ # @return [nil] if NoMethodError
63
78
  def parse_with_comments_buffer(buffer, backend: :auto)
64
79
  parser = parser_for(backend: backend)
80
+ return nil unless parser
81
+
65
82
  parser.parse_with_comments(buffer)
83
+ rescue NoMethodError
84
+ nil
66
85
  end
67
86
 
68
87
  private
@@ -70,16 +89,16 @@ module Docscribe
70
89
  # Build the backend-specific parser object.
71
90
  #
72
91
  # @private
73
- # @param [Symbol] backend
74
- # @return [Object]
92
+ # @param [Symbol] backend requested backend
93
+ # @return [Parser::Base, nil]
75
94
  def parser_for(backend: :auto)
76
95
  case backend(backend)
77
96
  when :parser
78
97
  require 'parser/current'
79
- Parser::CurrentRuby.new
98
+ Parser::CurrentRuby.new # steep:ignore
80
99
  when :prism
81
100
  require 'prism'
82
- Prism::Translation::ParserCurrent.new
101
+ Prism::Translation::ParserCurrent.new # steep:ignore
83
102
  end
84
103
  end
85
104
 
@@ -93,7 +112,7 @@ module Docscribe
93
112
  # @private
94
113
  # @param [Symbol] backend requested backend
95
114
  # @raise [ArgumentError]
96
- # @return [Symbol] :parser or :prism
115
+ # @return [Symbol, nil] :parser or :prism
97
116
  def backend(backend = :auto)
98
117
  env = ENV.fetch('DOCSCRIBE_PARSER_BACKEND') { nil }
99
118
  backend = env.to_sym if env && !env.empty?
@@ -40,9 +40,10 @@ module Docscribe
40
40
  # Each result is a Hash with:
41
41
  # - :anchor_node => Parser::AST::Node — node above which to insert doc
42
42
  # - :doc => String — complete doc block including newlines
43
+ #
43
44
  # @param [Parser::AST::Node] _ast AST node to analyze
44
45
  # @param [Parser::Source::Buffer] _buffer source buffer
45
- # @return [Array<Hash>]
46
+ # @return [Array<Hash<Symbol, Object>>]
46
47
  def collect(_ast, _buffer)
47
48
  []
48
49
  end
@@ -26,7 +26,6 @@ module Docscribe
26
26
  # Called once per documented method. Return [] if this plugin has
27
27
  # nothing to add for this particular method.
28
28
  #
29
- # @param [Docscribe::Plugin::Context] context method context snapshot
30
29
  # @param [Docscribe::Plugin::Context] _context method context snapshot (unused in default)
31
30
  # @return [Array<Docscribe::Plugin::Tag>]
32
31
  def call(_context)
@@ -2,27 +2,37 @@
2
2
 
3
3
  module Docscribe
4
4
  module Plugin
5
- # Snapshot of everything known about a method at doc-generation time.
5
+ # @!attribute [rw] node
6
+ # @return [Parser::AST::Node]
7
+ # @param [Parser::AST::Node] value
6
8
  #
7
- # Passed to every registered TagPlugin. Read-only — plugins must not
8
- # mutate the context.
9
+ # @!attribute [rw] container
10
+ # @return [String]
11
+ # @param [String] value
9
12
  #
10
- # @!attribute node
11
- # @return [Parser::AST::Node] the :def or :defs AST node
12
- # @!attribute container
13
- # @return [String] e.g. "MyModule::MyClass" or "Object" for top-level
14
- # @!attribute scope
15
- # @return [Symbol] :instance or :class
16
- # @!attribute visibility
17
- # @return [Symbol] :public, :protected, or :private
18
- # @!attribute method_name
13
+ # @!attribute [rw] scope
19
14
  # @return [Symbol]
20
- # @!attribute inferred_params
21
- # @return [Hash{String => String}] name => inferred type
22
- # @!attribute inferred_return
23
- # @return [String] inferred return type
24
- # @!attribute source
25
- # @return [String] raw method source text
15
+ # @param [Symbol] value
16
+ #
17
+ # @!attribute [rw] visibility
18
+ # @return [Symbol]
19
+ # @param [Symbol] value
20
+ #
21
+ # @!attribute [rw] method_name
22
+ # @return [Symbol]
23
+ # @param [Symbol] value
24
+ #
25
+ # @!attribute [rw] inferred_params
26
+ # @return [Hash<String, String>]
27
+ # @param [Hash<String, String>] value
28
+ #
29
+ # @!attribute [rw] inferred_return
30
+ # @return [String]
31
+ # @param [String] value
32
+ #
33
+ # @!attribute [rw] source
34
+ # @return [String]
35
+ # @param [String] value
26
36
  Context = Struct.new(
27
37
  :node,
28
38
  :container,
@@ -16,12 +16,12 @@ module Docscribe
16
16
  # @param [Object] value
17
17
  #
18
18
  # @!attribute [rw] priority
19
- # @return [Object]
20
- # @param [Object] value
19
+ # @return [Integer]
20
+ # @param [Integer] value
21
21
  #
22
22
  # @!attribute [rw] order
23
- # @return [Object]
24
- # @param [Object] value
23
+ # @return [Integer]
24
+ # @param [Integer] value
25
25
  Entry = Struct.new(:plugin, :priority, :order, keyword_init: true)
26
26
 
27
27
  @tag_entries = []
@@ -38,11 +38,9 @@ module Docscribe
38
38
  # - responds to #call => tag plugin (duck typing)
39
39
  # - responds to #collect => collector plugin (duck typing)
40
40
  #
41
- # @note module_function: when included, also defines #register (instance visibility: private)
41
+ # @note module_function: defines #register (visibility: private)
42
42
  # @param [Object] plugin plugin instance
43
43
  # @param [Integer] priority plugin priority (higher wins for conflicts)
44
- # @raise [ArgumentError] if plugin type cannot be determined
45
- # @raise [StandardError]
46
44
  # @return [void]
47
45
  def register(plugin, priority: 0)
48
46
  prio = parse_priority(priority)
@@ -52,11 +50,12 @@ module Docscribe
52
50
 
53
51
  # Parse and validate plugin priority.
54
52
  #
55
- # @note module_function: when included, also defines #parse_priority (instance visibility: private)
56
- # @param [Object] priority
57
- # @raise [ArgumentError]
53
+ # @note module_function: defines #parse_priority (visibility: private)
54
+ # @param [String, Integer] priority plugin priority (higher wins for conflicts)
58
55
  # @raise [StandardError]
59
- # @return [Integer]
56
+ # @raise [ArgumentError]
57
+ # @return [Integer] if StandardError
58
+ # @return [Object] if StandardError
60
59
  def parse_priority(priority)
61
60
  Integer(priority)
62
61
  rescue StandardError
@@ -65,10 +64,10 @@ module Docscribe
65
64
 
66
65
  # Create a new Entry with the next order number.
67
66
  #
68
- # @note module_function: when included, also defines #create_entry (instance visibility: private)
69
- # @param [Object] plugin
70
- # @param [Integer] priority
71
- # @return [Entry]
67
+ # @note module_function: defines #create_entry (visibility: private)
68
+ # @param [Object] plugin plugin instance
69
+ # @param [Integer] priority plugin priority (higher wins for conflicts)
70
+ # @return [Docscribe::Plugin::Registry::Entry]
72
71
  def create_entry(plugin, priority)
73
72
  @order_seq += 1
74
73
  Entry.new(plugin: plugin, priority: priority, order: @order_seq)
@@ -76,9 +75,9 @@ module Docscribe
76
75
 
77
76
  # Route entry to tag or collector list.
78
77
  #
79
- # @note module_function: when included, also defines #route_entry (instance visibility: private)
80
- # @param [Entry] entry
81
- # @param [Object] plugin
78
+ # @note module_function: defines #route_entry (visibility: private)
79
+ # @param [Docscribe::Plugin::Registry::Entry] entry the entry to route
80
+ # @param [Object] plugin plugin instance
82
81
  # @raise [ArgumentError]
83
82
  # @return [void]
84
83
  def route_entry(entry, plugin)
@@ -93,32 +92,32 @@ module Docscribe
93
92
 
94
93
  # All registered tag plugins in registration order.
95
94
  #
96
- # @note module_function: when included, also defines #tag_plugins (instance visibility: private)
97
- # @return [Array<#call>]
95
+ # @note module_function: defines #tag_plugins (visibility: private)
96
+ # @return [Array<Object>]
98
97
  def tag_plugins
99
98
  @tag_entries.map(&:plugin)
100
99
  end
101
100
 
102
101
  # All registered collector plugins in registration order.
103
102
  #
104
- # @note module_function: when included, also defines #collector_plugins (instance visibility: private)
105
- # @return [Array<#collect>]
103
+ # @note module_function: defines #collector_plugins (visibility: private)
104
+ # @return [Array<Object>]
106
105
  def collector_plugins
107
106
  @collector_entries.map(&:plugin)
108
107
  end
109
108
 
110
109
  # All registered tag plugin entries (plugin + priority metadata).
111
110
  #
112
- # @note module_function: when included, also defines #tag_entries (instance visibility: private)
113
- # @return [Array<Entry>]
111
+ # @note module_function: defines #tag_entries (visibility: private)
112
+ # @return [Array<Docscribe::Plugin::Registry::Entry>]
114
113
  def tag_entries
115
114
  @tag_entries.dup
116
115
  end
117
116
 
118
117
  # All registered collector plugin entries (plugin + priority metadata).
119
118
  #
120
- # @note module_function: when included, also defines #collector_entries (instance visibility: private)
121
- # @return [Array<Entry>]
119
+ # @note module_function: defines #collector_entries (visibility: private)
120
+ # @return [Array<Docscribe::Plugin::Registry::Entry>]
122
121
  def collector_entries
123
122
  @collector_entries.dup
124
123
  end
@@ -127,7 +126,7 @@ module Docscribe
127
126
  #
128
127
  # Primarily used in tests to reset state between examples.
129
128
  #
130
- # @note module_function: when included, also defines #clear! (instance visibility: private)
129
+ # @note module_function: defines #clear! (visibility: private)
131
130
  # @return [void]
132
131
  def clear!
133
132
  @tag_entries.clear
@@ -2,22 +2,17 @@
2
2
 
3
3
  module Docscribe
4
4
  module Plugin
5
- # A single YARD-style tag returned by a TagPlugin.
5
+ # @!attribute [rw] name
6
+ # @return [String]
7
+ # @param [String] value
6
8
  #
7
- # @example Simple tag
8
- # Tag.new(name: 'since', text: '1.3.0')
9
- # # => # @since 1.3.0
9
+ # @!attribute [rw] text
10
+ # @return [String, nil]
11
+ # @param [String, nil] value
10
12
  #
11
- # @example Tag with types
12
- # Tag.new(name: 'raise', types: ['ArgumentError'], text: 'if name is nil')
13
- # # => # @raise [ArgumentError] if name is nil
14
- #
15
- # @!attribute name
16
- # @return [String] tag name without leading @
17
- # @!attribute text
18
- # @return [String, nil] text after the type bracket
19
- # @!attribute types
20
- # @return [Array<String>, nil] optional type list rendered as [Foo, Bar]
13
+ # @!attribute [rw] types
14
+ # @return [Array<String>, nil]
15
+ # @param [Array<String>, nil] value
21
16
  Tag = Struct.new(:name, :text, :types, keyword_init: true)
22
17
  end
23
18
  end
@@ -23,7 +23,7 @@ module Docscribe
23
23
  # Errors in individual plugins are caught so one broken plugin does not
24
24
  # abort the entire run.
25
25
  #
26
- # @param [Docscribe::Plugin::Context] context
26
+ # @param [Docscribe::Plugin::Context] context plugin execution context
27
27
  # @raise [StandardError]
28
28
  # @return [Array<Docscribe::Plugin::Tag>]
29
29
  def self.run_tag_plugins(context)
@@ -43,10 +43,9 @@ module Docscribe
43
43
 
44
44
  # Run all registered CollectorPlugins for one file's AST.
45
45
  #
46
- # @param [Parser::AST::Node] ast
47
- # @param [Parser::Source::Buffer] buffer
48
- # @raise [StandardError]
49
- # @return [Array<Hash>]
46
+ # @param [Parser::AST::Node] ast parsed AST root node
47
+ # @param [Parser::Source::Buffer] buffer source buffer for AST
48
+ # @return [Array<Hash<Symbol, Object>>]
50
49
  def self.run_collector_plugins(ast, buffer)
51
50
  Registry.collector_entries.flat_map { |entry| process_single_plugin_result(entry, ast, buffer) }
52
51
  end
@@ -55,11 +54,12 @@ module Docscribe
55
54
  #
56
55
  # Merges plugin metadata into each hash insertion and handles errors.
57
56
  #
58
- # @param [Entry] entry
59
- # @param [Parser::AST::Node] ast
60
- # @param [Parser::Source::Buffer] buffer
57
+ # @param [Docscribe::Plugin::Registry::Entry] entry registry entry with priority and order metadata
58
+ # @param [Parser::AST::Node] ast parsed AST root node
59
+ # @param [Parser::Source::Buffer] buffer source buffer for AST
61
60
  # @raise [StandardError]
62
- # @return [Array<Hash>]
61
+ # @return [Array<Hash<Symbol, Object>>] if StandardError
62
+ # @return [Array] if StandardError
63
63
  def self.process_single_plugin_result(entry, ast, buffer)
64
64
  plugin = entry.plugin
65
65
  results = Array(plugin.collect(ast, buffer))
@@ -71,10 +71,10 @@ module Docscribe
71
71
 
72
72
  # Merge plugin metadata into collector results and filter invalid ones.
73
73
  #
74
- # @param [Array] results collector plugin results to process
75
- # @param [Entry] entry registry entry with priority and order metadata
76
- # @param [Base::CollectorPlugin] plugin the collector plugin instance
77
- # @return [Array<Hash>]
74
+ # @param [Array<Hash<Symbol, Object>>] results collector plugin results to process
75
+ # @param [Docscribe::Plugin::Registry::Entry] entry registry entry with priority and order metadata
76
+ # @param [Object] plugin the collector plugin instance
77
+ # @return [Array<Hash<Symbol, Object>>]
78
78
  def self.process_plugin_insertions(results, entry, plugin)
79
79
  results.map do |insertion|
80
80
  next nil unless valid_plugin_result?(insertion, plugin)
@@ -89,9 +89,8 @@ module Docscribe
89
89
 
90
90
  # Validate a CollectorPlugin result is a Hash.
91
91
  #
92
- # @private
93
- # @param [Object] insertion
94
- # @param [Object] plugin
92
+ # @param [Object] insertion collector plugin result
93
+ # @param [Object] plugin the collector plugin instance
95
94
  # @return [Boolean]
96
95
  def self.valid_plugin_result?(insertion, plugin)
97
96
  return true if insertion.is_a?(Hash)
@@ -100,6 +99,8 @@ module Docscribe
100
99
  false
101
100
  end
102
101
 
102
+ # Self
103
+ #
103
104
  # @return [Boolean]
104
105
  def self.debug?
105
106
  ENV['DOCSCRIBE_DEBUG'] == '1'
@@ -12,8 +12,10 @@ module Docscribe
12
12
  # - Sorbet RBI files
13
13
  # - RBS files
14
14
  class ProviderChain
15
- # @param [Array<#signature_for>] providers ordered signature providers
16
- # @return [Object]
15
+ # Initialize
16
+ #
17
+ # @param [Array<Docscribe::Types::_Provider>] providers ordered signature providers
18
+ # @return [void]
17
19
  def initialize(*providers)
18
20
  @providers = providers.compact
19
21
  end
@@ -30,14 +30,14 @@ module Docscribe
30
30
  # - lock-file is absent (collection not initialized)
31
31
  # - resolved directory does not exist on disk (collection not installed)
32
32
  #
33
- # @note module_function: when included, also defines #resolve (instance visibility: private)
33
+ # @note module_function: defines #resolve (visibility: private)
34
34
  # @param [String] root project root to search from
35
35
  # @return [String, nil] absolute path to the collection directory, or nil
36
36
  def resolve(root: Dir.pwd)
37
37
  lock = Pathname(root).join(LOCK_FILE)
38
38
  return nil unless lock.file?
39
39
 
40
- data = YAML.safe_load(lock.read, permitted_classes: [Symbol]) || {}
40
+ data = YAML.safe_load(lock.read, permitted_classes: [Symbol]) || {} # steep:ignore
41
41
  rel = data['path'] || DEFAULT_COLLECTION_PATH
42
42
 
43
43
  resolved = Pathname(root).join(rel)