docscribe 1.4.2 → 1.5.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +601 -139
  3. data/exe/docscribe-client +105 -0
  4. data/lib/docscribe/cli/check_for_comments.rb +183 -0
  5. data/lib/docscribe/cli/config_builder.rb +107 -53
  6. data/lib/docscribe/cli/formatters/json.rb +294 -0
  7. data/lib/docscribe/cli/formatters/sarif.rb +235 -0
  8. data/lib/docscribe/cli/formatters/text.rb +208 -0
  9. data/lib/docscribe/cli/formatters.rb +26 -0
  10. data/lib/docscribe/cli/generate.rb +56 -62
  11. data/lib/docscribe/cli/init.rb +14 -6
  12. data/lib/docscribe/cli/options.rb +206 -89
  13. data/lib/docscribe/cli/rbs_gen.rb +529 -0
  14. data/lib/docscribe/cli/run.rb +433 -154
  15. data/lib/docscribe/cli/server.rb +135 -0
  16. data/lib/docscribe/cli/sigs.rb +366 -0
  17. data/lib/docscribe/cli/update_types.rb +103 -0
  18. data/lib/docscribe/cli.rb +21 -24
  19. data/lib/docscribe/config/defaults.rb +7 -2
  20. data/lib/docscribe/config/emit.rb +17 -0
  21. data/lib/docscribe/config/filtering.rb +17 -24
  22. data/lib/docscribe/config/loader.rb +19 -17
  23. data/lib/docscribe/config/plugin.rb +1 -1
  24. data/lib/docscribe/config/rbs.rb +39 -7
  25. data/lib/docscribe/config/sorbet.rb +22 -16
  26. data/lib/docscribe/config/sorting.rb +1 -1
  27. data/lib/docscribe/config/template.rb +10 -1
  28. data/lib/docscribe/config/utils.rb +11 -9
  29. data/lib/docscribe/config.rb +10 -6
  30. data/lib/docscribe/infer/ast_walk.rb +1 -1
  31. data/lib/docscribe/infer/literals.rb +6 -11
  32. data/lib/docscribe/infer/names.rb +2 -3
  33. data/lib/docscribe/infer/params.rb +14 -16
  34. data/lib/docscribe/infer/raises.rb +3 -5
  35. data/lib/docscribe/infer/returns.rb +615 -151
  36. data/lib/docscribe/infer.rb +29 -26
  37. data/lib/docscribe/inline_rewriter/collector.rb +159 -164
  38. data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
  39. data/lib/docscribe/inline_rewriter/doc_builder.rb +1032 -723
  40. data/lib/docscribe/inline_rewriter/source_helpers.rb +48 -48
  41. data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
  42. data/lib/docscribe/inline_rewriter.rb +485 -488
  43. data/lib/docscribe/lru_cache.rb +49 -0
  44. data/lib/docscribe/parsing.rb +28 -9
  45. data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
  46. data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
  47. data/lib/docscribe/plugin/context.rb +28 -18
  48. data/lib/docscribe/plugin/registry.rb +25 -26
  49. data/lib/docscribe/plugin/tag.rb +9 -14
  50. data/lib/docscribe/plugin.rb +17 -16
  51. data/lib/docscribe/server.rb +608 -0
  52. data/lib/docscribe/types/provider_chain.rb +4 -2
  53. data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
  54. data/lib/docscribe/types/rbs/provider.rb +177 -51
  55. data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
  56. data/lib/docscribe/types/signature.rb +22 -42
  57. data/lib/docscribe/types/sorbet/base_provider.rb +29 -21
  58. data/lib/docscribe/types/sorbet/rbi_provider.rb +6 -5
  59. data/lib/docscribe/types/sorbet/source_provider.rb +6 -4
  60. data/lib/docscribe/types/yard/formatter.rb +100 -0
  61. data/lib/docscribe/types/yard/parser.rb +240 -0
  62. data/lib/docscribe/types/yard/types.rb +52 -0
  63. data/lib/docscribe/version.rb +1 -1
  64. metadata +38 -1
@@ -139,5 +139,22 @@ module Docscribe
139
139
  def include_param_documentation?
140
140
  fetch_bool(%w[emit include_param_documentation], true)
141
141
  end
142
+
143
+ # Whether to preserve existing @param/@return descriptions in aggressive mode.
144
+ #
145
+ # @return [Boolean]
146
+ def keep_descriptions?
147
+ fetch_bool(%w[keep_descriptions], false)
148
+ end
149
+
150
+ # Whether to skip @param generation for anonymous block arguments (&).
151
+ #
152
+ # Ruby 3.2+ allows `def foo(&)`. When enabled, no @param is generated
153
+ # for anonymous block parameters since they have no name to reference.
154
+ #
155
+ # @return [Boolean]
156
+ def skip_anonymous_block_params?
157
+ fetch_bool(%w[skip_anonymous_block_params], false)
158
+ end
142
159
  end
143
160
  end
@@ -9,7 +9,6 @@ module Docscribe
9
9
  # Exclude rules win. If no include rules are configured, files are included by default.
10
10
  #
11
11
  # @param [String] path file path to test
12
- # @raise [StandardError]
13
12
  # @return [Boolean]
14
13
  def process_file?(path)
15
14
  include_patterns, exclude_patterns = load_file_patterns
@@ -23,8 +22,7 @@ module Docscribe
23
22
 
24
23
  # Load normalized file include/exclude patterns from config.
25
24
  #
26
- # @private
27
- # @return [Array(Array<String>, Array<String>)] include_patterns, exclude_patterns
25
+ # @return [(Array<String>, Array<String>)]
28
26
  def load_file_patterns
29
27
  files = raw.dig('filter', 'files') || {}
30
28
  [normalize_file_patterns(files['include']), normalize_file_patterns(files['exclude'])]
@@ -32,10 +30,10 @@ module Docscribe
32
30
 
33
31
  # Compute the relative path for filtering.
34
32
  #
35
- # @private
36
- # @param [String] path
33
+ # @param [String] path file path to test
37
34
  # @raise [StandardError]
38
35
  # @return [String]
36
+ # @return [String] if StandardError
39
37
  def relative_path(path)
40
38
  Pathname.new(path).expand_path.relative_path_from(Pathname.pwd).cleanpath.to_s
41
39
  rescue StandardError
@@ -76,8 +74,7 @@ module Docscribe
76
74
  # - remove empties
77
75
  # - expand shorthand directory forms
78
76
  #
79
- # @private
80
- # @param [Array<String>, nil] list raw pattern list
77
+ # @param [Array<String>?] list raw pattern list
81
78
  # @return [Array<String>]
82
79
  def normalize_file_patterns(list)
83
80
  Array(list).compact.map(&:to_s).reject(&:empty?).flat_map { |pat| expand_directory_shorthand(pat) }.uniq
@@ -89,8 +86,7 @@ module Docscribe
89
86
  # - `"spec/"` => `"spec/**/*"`
90
87
  # - `"spec"` => `"spec/**/*"` if `spec` exists as a directory
91
88
  #
92
- # @private
93
- # @param [String] pattern
89
+ # @param [String] pattern file pattern to expand
94
90
  # @return [Array<String>]
95
91
  def expand_directory_shorthand(pattern)
96
92
  pat = pattern.dup
@@ -106,9 +102,8 @@ module Docscribe
106
102
 
107
103
  # Check whether a file path matches any configured file pattern.
108
104
  #
109
- # @private
110
- # @param [Array<String>] patterns
111
- # @param [String] path
105
+ # @param [Array<String>] patterns file filter patterns
106
+ # @param [String] path file path to test
112
107
  # @return [Boolean]
113
108
  def file_matches_any?(patterns, path)
114
109
  patterns.any? { |pat| file_match_pattern?(pat, path) }
@@ -121,13 +116,12 @@ module Docscribe
121
116
  # - globs
122
117
  # - recursive glob shorthand normalization
123
118
  #
124
- # @private
125
- # @param [String] pattern
126
- # @param [String] path
119
+ # @param [String] pattern file filter pattern
120
+ # @param [String] path file path to test
127
121
  # @return [Boolean]
128
122
  def file_match_pattern?(pattern, path)
129
123
  if pattern.start_with?('/') && pattern.end_with?('/') && pattern.length >= 2
130
- return Regexp.new(pattern[1..-2]).match?(path)
124
+ return Regexp.new(pattern[1..-2]).match?(path) # steep:ignore
131
125
  end
132
126
 
133
127
  patterns_to_try = [pattern]
@@ -140,34 +134,33 @@ module Docscribe
140
134
 
141
135
  # Allowed method scopes from config/defaults.
142
136
  #
143
- # @private
144
137
  # @return [Array<String>]
145
138
  def filter_scopes
146
- Array(raw.dig('filter', 'scopes') || DEFAULT.dig('filter', 'scopes')).map(&:to_s)
139
+ Array(raw.dig('filter', 'scopes') || DEFAULT.dig('filter', 'scopes')).map(&:to_s) # steep:ignore
147
140
  end
148
141
 
149
142
  # Allowed method visibilities from config/defaults.
150
143
  #
151
- # @private
152
144
  # @return [Array<String>]
153
145
  def filter_visibilities
154
- Array(raw.dig('filter', 'visibilities') || DEFAULT.dig('filter', 'visibilities')).map(&:to_s)
146
+ Array(raw.dig('filter', 'visibilities') ||
147
+ DEFAULT.dig('filter', 'visibilities')).map(&:to_s) # steep:ignore
155
148
  end
156
149
 
157
150
  # Exclude method filter patterns.
158
151
  #
159
- # @private
160
152
  # @return [Array<String>]
161
153
  def filter_exclude_patterns
162
- Array(raw.dig('filter', 'exclude') || DEFAULT.dig('filter', 'exclude')).map(&:to_s).reject(&:empty?)
154
+ Array(raw.dig('filter', 'exclude') ||
155
+ DEFAULT.dig('filter', 'exclude')).map(&:to_s).reject(&:empty?) # steep:ignore
163
156
  end
164
157
 
165
158
  # Include method filter patterns.
166
159
  #
167
- # @private
168
160
  # @return [Array<String>]
169
161
  def filter_include_patterns
170
- Array(raw.dig('filter', 'include') || DEFAULT.dig('filter', 'include')).map(&:to_s).reject(&:empty?)
162
+ Array(raw.dig('filter', 'include') ||
163
+ DEFAULT.dig('filter', 'include')).map(&:to_s).reject(&:empty?) # steep:ignore
171
164
  end
172
165
  end
173
166
  end
@@ -10,16 +10,14 @@ module Docscribe
10
10
  # - `docscribe.yml` in the current directory, if present
11
11
  # - otherwise defaults only
12
12
  #
13
- # @param [String, nil] path optional config path
13
+ # @param [String?] path optional config path
14
14
  # @return [Docscribe::Config]
15
15
  def self.load(path = nil)
16
16
  raw = {} #: Hash[String, untyped]
17
- if path && File.file?(path)
18
- raw = safe_load_file_compat(path)
19
- elsif File.file?('docscribe.yml')
20
- raw = safe_load_file_compat('docscribe.yml')
21
- end
22
- new(raw)
17
+ resolved = path if path && File.file?(path)
18
+ resolved ||= 'docscribe.yml' if File.file?('docscribe.yml')
19
+ raw = safe_load_file_compat(resolved) if resolved
20
+ new(config_path: resolved, **raw) # steep:ignore
23
21
  end
24
22
 
25
23
  # Safely load YAML from a file across Ruby/Psych versions.
@@ -28,11 +26,13 @@ module Docscribe
28
26
  # and calling {safe_load_compat}.
29
27
  #
30
28
  # @param [String] path file path
31
- # @return [Hash]
29
+ # @return [Hash<String, Object>]
32
30
  def self.safe_load_file_compat(path)
33
- if YAML.respond_to?(:safe_load_file)
34
- YAML.safe_load_file(path,
35
- permitted_classes: [], permitted_symbols: [],
31
+ if YAML.respond_to?(:safe_load_file) # steep:ignore
32
+ pclasses = [] #: Array[String]
33
+ psymbols = [] #: Array[Symbol]
34
+ YAML.safe_load_file(path, # steep:ignore
35
+ permitted_classes: pclasses, permitted_symbols: psymbols,
36
36
  aliases: true) || {} #: Hash[String, untyped]
37
37
  else
38
38
  yaml = File.open(path, 'r:bom|utf-8', &:read)
@@ -43,20 +43,22 @@ module Docscribe
43
43
  # Safely load YAML from a string across Psych API versions.
44
44
  #
45
45
  # @param [String] yaml YAML document
46
- # @param [String, nil] filename optional filename for diagnostics
46
+ # @param [String?] filename optional filename for diagnostics
47
47
  # @raise [ArgumentError]
48
- # @return [Hash]
48
+ # @return [Hash<String, Object>]
49
+ # @return [Object] if ArgumentError
49
50
  def self.safe_load_compat(yaml, filename: nil)
50
- Psych.safe_load(
51
+ pclasses = [] #: Array[String]
52
+ psymbols = [] #: Array[Symbol]
53
+ Psych.safe_load( # steep:ignore
51
54
  yaml,
52
- permitted_classes: [],
53
- permitted_symbols: [],
55
+ permitted_classes: pclasses, permitted_symbols: psymbols,
54
56
  aliases: true,
55
57
  filename: filename
56
58
  ) #: Hash[String, untyped]
57
59
  rescue ArgumentError
58
60
  # Older Psych signature uses positional args
59
- Psych.safe_load(yaml, [], [], true, filename)
61
+ Psych.safe_load(yaml, [], [], true, filename) # steep:ignore
60
62
  end
61
63
  end
62
64
  end
@@ -15,7 +15,7 @@ module Docscribe
15
15
  # @raise [LoadError]
16
16
  # @return [void]
17
17
  def load_plugins!
18
- paths = Array(raw.dig('plugins', 'require')).compact
18
+ paths = Array(raw.dig('plugins', 'require')).compact #: Array[String]
19
19
  return if paths.empty?
20
20
 
21
21
  require 'docscribe/plugin'
@@ -8,7 +8,6 @@ module Docscribe
8
8
  # If RBS cannot be loaded, this returns nil and Docscribe falls back to
9
9
  # inference.
10
10
  #
11
- # @raise [LoadError]
12
11
  # @return [Docscribe::Types::RBS::Provider, nil]
13
12
  def rbs_provider
14
13
  return nil unless rbs_enabled?
@@ -24,14 +23,25 @@ module Docscribe
24
23
  fetch_bool(%w[rbs enabled], false)
25
24
  end
26
25
 
27
- # @raise [LoadError]
28
- # @return [Object]
26
+ # Core rbs provider
27
+ #
28
+ # @return [Docscribe::Types::RBS::Provider, nil]
29
29
  def core_rbs_provider
30
30
  return nil unless ruby_supports_rbs?
31
31
 
32
32
  @core_rbs_provider ||= build_core_rbs_provider
33
33
  end
34
34
 
35
+ # Whether to warn when rbs_collection.lock.yaml exists but --rbs-collection
36
+ # was not passed.
37
+ #
38
+ # Set `rbs.warn_missing_collection: false` in `docscribe.yml` to suppress.
39
+ #
40
+ # @return [Boolean]
41
+ def rbs_warn_missing_collection?
42
+ fetch_bool(%w[rbs warn_missing_collection], true)
43
+ end
44
+
35
45
  private
36
46
 
37
47
  # Check whether the current Ruby version supports RBS (requires 3.0+).
@@ -48,23 +58,31 @@ module Docscribe
48
58
  false
49
59
  end
50
60
 
61
+ # Build rbs provider
62
+ #
51
63
  # @private
52
64
  # @raise [LoadError]
53
65
  # @return [Docscribe::Types::RBS::Provider, nil]
66
+ # @return [nil] if LoadError
54
67
  def build_rbs_provider
55
68
  require 'docscribe/types/rbs/provider'
56
69
  Docscribe::Types::RBS::Provider.new(
57
70
  sig_dirs: rbs_sig_dirs,
58
71
  collection_dirs: rbs_collection_dirs,
59
- collapse_generics: rbs_collapse_generics?
72
+ collapse_generics: rbs_collapse_generics?,
73
+ collapse_object_generics: rbs_collapse_object_generics?
60
74
  )
61
75
  rescue LoadError
76
+ warn 'Docscribe: --rbs requires the `rbs` gem. Add `gem "rbs"` to your Gemfile and run `bundle install`.'
62
77
  nil
63
78
  end
64
79
 
80
+ # Build core rbs provider
81
+ #
65
82
  # @private
66
83
  # @raise [LoadError]
67
84
  # @return [Docscribe::Types::RBS::Provider, nil]
85
+ # @return [nil] if LoadError
68
86
  def build_core_rbs_provider
69
87
  require 'docscribe/types/rbs/provider'
70
88
  Docscribe::Types::RBS::Provider.new(
@@ -80,7 +98,7 @@ module Docscribe
80
98
  # @private
81
99
  # @return [Array<String>]
82
100
  def rbs_sig_dirs
83
- Array(raw.dig('rbs', 'sig_dirs') || DEFAULT.dig('rbs', 'sig_dirs')).map(&:to_s)
101
+ Array(raw.dig('rbs', 'sig_dirs') || DEFAULT.dig('rbs', 'sig_dirs')).map(&:to_s) # steep:ignore
84
102
  end
85
103
 
86
104
  # RBS collection directories (auto-discovered from rbs_collection.lock.yaml).
@@ -92,7 +110,7 @@ module Docscribe
92
110
  # @private
93
111
  # @return [Array<String>]
94
112
  def rbs_collection_dirs
95
- Array(raw.dig('rbs', 'collection_dirs')).map(&:to_s)
113
+ Array(raw.dig('rbs', 'collection_dirs')).map(&:to_s) # steep:ignore
96
114
  end
97
115
 
98
116
  # Whether generic RBS types should be collapsed to simpler container names.
@@ -102,9 +120,23 @@ module Docscribe
102
120
  # - `Array<Integer>` => `Array`
103
121
  #
104
122
  # @private
105
- # @return [Object]
123
+ # @return [Boolean]
106
124
  def rbs_collapse_generics?
107
125
  fetch_bool(%w[rbs collapse_generics], false)
108
126
  end
127
+
128
+ # Whether to collapse generic types when all inner types are Object.
129
+ #
130
+ # Unlike `collapse_generics` (which drops all generic info), this only
131
+ # collapses when the type arguments provide no useful information:
132
+ # - `Hash<Symbol, Object>` => stays as is (Symbol is useful)
133
+ # - `Hash<Object, Object>` => `Hash`
134
+ # - `Array<Object>` => `Array`
135
+ #
136
+ # @private
137
+ # @return [Boolean]
138
+ def rbs_collapse_object_generics?
139
+ fetch_bool(%w[rbs collapse_object_generics], false)
140
+ end
109
141
  end
110
142
  end
@@ -14,7 +14,6 @@ module Docscribe
14
14
  #
15
15
  # @param [String] source Ruby source being rewritten
16
16
  # @param [String] file source name for diagnostics
17
- # @raise [LoadError]
18
17
  # @return [Docscribe::Types::ProviderChain, nil]
19
18
  def signature_provider_for(source:, file:)
20
19
  providers = [] #: Array[untyped]
@@ -25,10 +24,9 @@ module Docscribe
25
24
 
26
25
  # Append Sorbet-based providers to the list.
27
26
  #
28
- # @private
29
- # @param [Array] providers
30
- # @param [String] source
31
- # @param [String] file
27
+ # @param [Array<Object>] providers provider list to populate
28
+ # @param [String] source Ruby source being rewritten
29
+ # @param [String] file source name for diagnostics
32
30
  # @return [void]
33
31
  def append_sorbet_providers(providers, source:, file:)
34
32
  return unless sorbet_enabled?
@@ -39,17 +37,18 @@ module Docscribe
39
37
 
40
38
  # Build a Sorbet source provider (inline sigs).
41
39
  #
42
- # @private
43
- # @param [String] source
44
- # @param [String] file
40
+ # @param [String] source Ruby source being rewritten
41
+ # @param [String] file source name for diagnostics
45
42
  # @raise [LoadError]
46
43
  # @return [Docscribe::Types::Sorbet::SourceProvider, nil]
44
+ # @return [nil] if LoadError
47
45
  def sorbet_source_provider(source, file)
48
46
  require 'docscribe/types/sorbet/source_provider'
49
47
  Docscribe::Types::Sorbet::SourceProvider.new(
50
48
  source: source,
51
49
  file: file,
52
- collapse_generics: sorbet_collapse_generics?
50
+ collapse_generics: sorbet_collapse_generics?,
51
+ collapse_object_generics: sorbet_collapse_object_generics?
53
52
  )
54
53
  rescue LoadError
55
54
  nil
@@ -57,8 +56,7 @@ module Docscribe
57
56
 
58
57
  # Build the provider chain from a non-empty list, or return nil.
59
58
  #
60
- # @private
61
- # @param [Array] providers
59
+ # @param [Array<Object>] providers provider list to chain
62
60
  # @return [Docscribe::Types::ProviderChain, nil]
63
61
  def build_provider_chain(providers)
64
62
  providers = providers.compact
@@ -77,10 +75,9 @@ module Docscribe
77
75
 
78
76
  @sorbet_rbi_provider ||= begin
79
77
  require 'docscribe/types/sorbet/rbi_provider'
80
- Docscribe::Types::Sorbet::RBIProvider.new(
81
- rbi_dirs: sorbet_rbi_dirs,
82
- collapse_generics: sorbet_collapse_generics?
83
- )
78
+ Docscribe::Types::Sorbet::RBIProvider.new(rbi_dirs: sorbet_rbi_dirs,
79
+ collapse_generics: sorbet_collapse_generics?,
80
+ collapse_object_generics: sorbet_collapse_object_generics?)
84
81
  rescue LoadError
85
82
  nil
86
83
  end
@@ -97,7 +94,7 @@ module Docscribe
97
94
  #
98
95
  # @return [Array<String>]
99
96
  def sorbet_rbi_dirs
100
- Array(raw.dig('sorbet', 'rbi_dirs') || DEFAULT.dig('sorbet', 'rbi_dirs')).map(&:to_s)
97
+ Array(raw.dig('sorbet', 'rbi_dirs') || DEFAULT.dig('sorbet', 'rbi_dirs')).map(&:to_s) # steep:ignore
101
98
  end
102
99
 
103
100
  # Whether generic Sorbet/RBI container types should be simplified.
@@ -109,5 +106,14 @@ module Docscribe
109
106
  def sorbet_collapse_generics?
110
107
  fetch_bool(%w[sorbet collapse_generics], rbs_collapse_generics?)
111
108
  end
109
+
110
+ # Whether to collapse Object-typed generics in Sorbet types.
111
+ #
112
+ # Falls back to the RBS setting when Sorbet-specific config is not present.
113
+ #
114
+ # @return [Boolean]
115
+ def sorbet_collapse_object_generics?
116
+ fetch_bool(%w[sorbet collapse_object_generics], rbs_collapse_object_generics?)
117
+ end
112
118
  end
113
119
  end
@@ -17,7 +17,7 @@ module Docscribe
17
17
  # @return [Array<String>]
18
18
  def tag_order
19
19
  Array(raw.dig('doc', 'tag_order') || DEFAULT.dig('doc', 'tag_order')).map do |t|
20
- t.to_s.sub(/\A@/, '')
20
+ t.to_s.sub(/\A@/, '') # steep:ignore
21
21
  end
22
22
  end
23
23
  end
@@ -7,7 +7,6 @@ module Docscribe
7
7
  #
8
8
  # The template documents the most common CLI workflows and all supported
9
9
  # configuration sections with comments.
10
- # @see Docscribe::Config::DEFAULT
11
10
  #
12
11
  # @return [String]
13
12
  def self.default_yaml
@@ -21,6 +20,7 @@ module Docscribe
21
20
  # bundle exec docscribe lib # check what would change
22
21
  # bundle exec docscribe -a lib # apply safe updates
23
22
  # bundle exec docscribe -A lib # rebuild all doc blocks
23
+ # bundle exec docscribe -AkB lib # rebuild, keep descriptions, no boilerplate
24
24
 
25
25
  emit:
26
26
  # What to include in generated documentation
@@ -89,13 +89,22 @@ module Docscribe
89
89
  sig_dirs: ["sig"]
90
90
  collection_dirs: [] # auto-discovered from --rbs-collection
91
91
  collapse_generics: false # Hash<Symbol, String> => Hash
92
+ collapse_object_generics: false # Hash<Object, Object> => Hash (keep if inner types are useful)
92
93
  collection: false # auto-discover from rbs_collection.lock.yaml
94
+ warn_missing_collection: true # warn if rbs_collection.lock.yaml exists without --rbs-collection
93
95
 
94
96
  sorbet:
95
97
  # Use Sorbet inline sigs and RBI files for better types
96
98
  enabled: false
97
99
  rbi_dirs: ["sorbet/rbi", "rbi"]
98
100
  collapse_generics: false
101
+ collapse_object_generics: false
102
+
103
+ # Preserve existing @param/@return descriptions in aggressive mode
104
+ keep_descriptions: false
105
+
106
+ # Skip @param for anonymous block arguments (&) (Ruby 3.2+)
107
+ skip_anonymous_block_params: false
99
108
 
100
109
  plugins:
101
110
  # Load custom plugins
@@ -46,7 +46,7 @@ module Docscribe
46
46
  # Convert an internal scope symbol into the config key used under `methods`.
47
47
  #
48
48
  # @private
49
- # @param [Symbol] scope
49
+ # @param [Symbol] scope :instance or :class
50
50
  # @return [String]
51
51
  def scope_to_key(scope)
52
52
  scope == :class ? 'class' : 'instance'
@@ -55,8 +55,8 @@ module Docscribe
55
55
  # Check whether any pattern matches the given text.
56
56
  #
57
57
  # @private
58
- # @param [Array<String>] patterns
59
- # @param [String] text
58
+ # @param [Array<String>] patterns filter patterns to match
59
+ # @param [String] text text to test against patterns
60
60
  # @return [Boolean]
61
61
  def matches_any?(patterns, text)
62
62
  patterns.any? { |pat| match_pattern?(pat, text) }
@@ -69,12 +69,14 @@ module Docscribe
69
69
  # - shell-style glob patterns (with `/` translated to `#` since method IDs use `#`)
70
70
  #
71
71
  # @private
72
- # @param [String] pattern
73
- # @param [String] text
72
+ # @param [String] pattern filter pattern to match
73
+ # @param [String] text method ID to test
74
74
  # @return [Boolean]
75
75
  def match_pattern?(pattern, text)
76
76
  if pattern.start_with?('/') && pattern.end_with?('/') && pattern.length >= 2
77
- Regexp.new(pattern[1..-2]).match?(text)
77
+ Regexp.new(pattern[1..-2]).match?(text) # steep:ignore
78
+ elsif pattern.count('*?[{').zero?
79
+ File.fnmatch?("*#{pattern.tr('/', '#')}*", text, File::FNM_EXTGLOB)
78
80
  else
79
81
  File.fnmatch?(pattern.tr('/', '#'), text, File::FNM_EXTGLOB)
80
82
  end
@@ -85,9 +87,9 @@ module Docscribe
85
87
  # Nested hashes are merged recursively; non-hash values are replaced.
86
88
  #
87
89
  # @private
88
- # @param [Hash] hash1 base hash
89
- # @param [Hash, nil] hash2 override hash
90
- # @return [Hash]
90
+ # @param [Hash<Object, Object>] hash1 base hash
91
+ # @param [Hash<Object, Object>, nil] hash2 override hash
92
+ # @return [Hash<Object, Object>]
91
93
  def deep_merge(hash1, hash2)
92
94
  return hash1 unless hash2
93
95
 
@@ -7,20 +7,24 @@ require 'psych'
7
7
  module Docscribe
8
8
  # Application configuration with deep-merge defaults and overrides.
9
9
  class Config
10
- # Raw config hash after deep-merging user config with defaults.
11
- #
12
10
  # @!attribute [r] raw
13
- # @return [Hash]
11
+ # @return [Hash<String, Object>]
14
12
  attr_reader :raw
15
13
 
14
+ # @!attribute [r] config_path
15
+ # @return [String?]
16
+ attr_reader :config_path
17
+
16
18
  # Create a configuration object from a raw config hash.
17
19
  #
18
20
  # Missing keys are filled from {DEFAULT} via deep merge.
19
21
  #
20
- # @param [Hash, nil] raw user-provided config hash
22
+ # @param [String?] config_path optional path to the config file
23
+ # @param [Hash<String, Object>] raw user-provided config hash
21
24
  # @return [void]
22
- def initialize(raw = {})
23
- @raw = deep_merge(DEFAULT, raw || {})
25
+ def initialize(config_path: nil, **raw)
26
+ @raw = deep_merge(DEFAULT, raw)
27
+ @config_path = config_path
24
28
  end
25
29
  end
26
30
  end
@@ -11,7 +11,7 @@ module Docscribe
11
11
  # Yields each node exactly once, descending recursively through child nodes.
12
12
  # Non-AST values are ignored.
13
13
  #
14
- # @note module_function: when included, also defines #walk (instance visibility: private)
14
+ # @note module_function: defines #walk (visibility: private)
15
15
  # @param [Parser::AST::Node, nil] node root AST node
16
16
  # @param [Proc] block visitor block
17
17
  # @return [void]
@@ -17,7 +17,7 @@ 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]
@@ -30,8 +30,7 @@ module Docscribe
30
30
 
31
31
  # Map a node type symbol to a known literal type name.
32
32
  #
33
- # @note module_function: when included, also defines # (instance visibility: private)
34
- # @private
33
+ # @note module_function: defines #literal_type_for (visibility: private)
35
34
  # @param [Symbol] type node type
36
35
  # @return [String, nil]
37
36
  def literal_type_for(type)
@@ -40,10 +39,8 @@ module Docscribe
40
39
 
41
40
  # Extract a constant name from a `:const` node.
42
41
  #
43
- # @note module_function: when included, also defines # (instance visibility: private)
44
- # @private
45
- # @param [Parser::AST::Node] node
46
- # @param [String] fallback_type
42
+ # @note module_function: defines #const_type_for (visibility: private)
43
+ # @param [Parser::AST::Node] node literal/value node
47
44
  # @param [String] _fallback_type fallback type string (unused here)
48
45
  # @return [String, nil]
49
46
  def const_type_for(node, _fallback_type)
@@ -54,10 +51,8 @@ module Docscribe
54
51
 
55
52
  # Extract a type from a `Foo.new` send node.
56
53
  #
57
- # @note module_function: when included, also defines # (instance visibility: private)
58
- # @private
59
- # @param [Parser::AST::Node] node
60
- # @param [String] fallback_type
54
+ # @note module_function: defines #send_new_type_for (visibility: private)
55
+ # @param [Parser::AST::Node] node literal/value node
61
56
  # @param [String] _fallback_type fallback type string (unused here)
62
57
  # @return [String, nil]
63
58
  def send_new_type_for(node, _fallback_type)
@@ -15,7 +15,7 @@ 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)
18
+ # @note module_function: defines #const_full_name (visibility: private)
19
19
  # @param [Parser::AST::Node, nil] node constant-like AST node
20
20
  # @return [String, nil]
21
21
  def const_full_name(node)
@@ -31,8 +31,7 @@ module Docscribe
31
31
 
32
32
  # Build the fully qualified name from a `:const` node.
33
33
  #
34
- # @note module_function: when included, also defines # (instance visibility: private)
35
- # @private
34
+ # @note module_function: defines #build_const_full_name (visibility: private)
36
35
  # @param [Parser::AST::Node] node a `:const` node
37
36
  # @return [String]
38
37
  def build_const_full_name(node)