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
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ require 'docscribe/config/defaults'
6
+ require 'docscribe/config/loader'
7
+ require 'docscribe/config/emit'
8
+
9
+ module Docscribe
10
+ module CLI
11
+ # Check for default placeholder text in generated documentation.
12
+ #
13
+ # Usage:
14
+ # docscribe check_for_comments [paths...]
15
+ #
16
+ # Reads the configured placeholder messages from docscribe.yml (or defaults)
17
+ # and scans Ruby source files for YARD comments containing those placeholders.
18
+ # Exits non-zero if any are found.
19
+ module CheckForComments
20
+ BANNER = <<~TEXT
21
+ Usage: docscribe check_for_comments [paths...]
22
+
23
+ Check for default placeholder documentation text in Ruby source files.
24
+
25
+ Reads placeholder messages from docscribe.yml config (or built-in defaults)
26
+ and scans .rb files for YARD comments still containing that text.
27
+
28
+ Useful in CI to catch auto-generated text that should have been replaced.
29
+ TEXT
30
+
31
+ class << self
32
+ # @param [Array<String>] argv
33
+ # @return [Integer]
34
+ def run(argv)
35
+ config = Docscribe::Config.load
36
+ placeholders = resolve_placeholders(config)
37
+ return no_placeholders_configured if placeholders.empty?
38
+
39
+ parse_options(argv)
40
+ paths = expand_paths(argv)
41
+ return no_paths if paths.empty?
42
+
43
+ results = scan_paths(paths, placeholders)
44
+ process_results(results)
45
+ end
46
+
47
+ private
48
+
49
+ # @private
50
+ # @param [Docscribe::Config] config
51
+ # @return [Array<String>]
52
+ def resolve_placeholders(config)
53
+ default_msg = raw_or_default(config, %w[doc default_message])
54
+ param_doc = config.param_documentation
55
+ [default_msg, param_doc].compact.uniq
56
+ end
57
+
58
+ # @private
59
+ # @param [Docscribe::Config] config
60
+ # @param [Array<String>] keys nested keys
61
+ # @return [String, nil]
62
+ def raw_or_default(config, keys)
63
+ raw = config.raw.dig(*keys)
64
+ return raw if raw
65
+
66
+ Docscribe::Config::DEFAULT.dig(*keys)
67
+ end
68
+
69
+ # @private
70
+ # @return [Integer]
71
+ def no_placeholders_configured
72
+ warn 'Docscribe: No placeholder messages configured. Nothing to check.'
73
+ 0
74
+ end
75
+
76
+ # @private
77
+ # @param [Array<String>] argv
78
+ # @return [void]
79
+ def parse_options(argv)
80
+ OptionParser.new(BANNER) do |opts|
81
+ opts.on('-h', '--help', 'Show this help') { puts opts or exit 0 }
82
+ end.parse!(argv)
83
+ end
84
+
85
+ # @private
86
+ # @param [Array<String>] args
87
+ # @return [Array<String>]
88
+ def expand_paths(args)
89
+ files = [] #: Array[String]
90
+ args = ['.'] if args.empty?
91
+ args.each { |path| expand_single_path(files, path) }
92
+ files.uniq.sort
93
+ end
94
+
95
+ # @private
96
+ # @param [Array<String>] files
97
+ # @param [String] path
98
+ # @return [void]
99
+ def expand_single_path(files, path)
100
+ if File.directory?(path)
101
+ files.concat(Dir.glob(File.join(path, '**', '*.rb')))
102
+ elsif File.file?(path) && path.end_with?('.rb')
103
+ files << path
104
+ elsif File.file?(path)
105
+ warn "Skipping non-Ruby file: #{path}"
106
+ else
107
+ warn "Skipping missing path: #{path}"
108
+ end
109
+ end
110
+
111
+ # @private
112
+ # @return [Integer]
113
+ def no_paths
114
+ warn 'No files found. Pass files or directories (e.g. `docscribe check_for_comments lib`).'
115
+ 1
116
+ end
117
+
118
+ # @private
119
+ # @param [Array<String>] paths
120
+ # @param [Array<String>] placeholders
121
+ # @return [Array<(String, Array<(Integer, String)>)>]
122
+ def scan_paths(paths, placeholders)
123
+ paths.filter_map { |path| scan_file(path, placeholders) }
124
+ end
125
+
126
+ # @private
127
+ # @param [Array<(String, Array<(Integer, String)>)>] results
128
+ # @return [Integer]
129
+ def process_results(results)
130
+ if results.empty?
131
+ puts 'Docscribe: No placeholder documentation found.'
132
+ 0
133
+ else
134
+ report(results)
135
+ 1
136
+ end
137
+ end
138
+
139
+ # @private
140
+ # @param [String] path
141
+ # @param [Array<String>] placeholders
142
+ # @return [(String, Array<(Integer, String)>)?]
143
+ def scan_file(path, placeholders)
144
+ lines = File.readlines(path)
145
+ matches = [] #: Array[[Integer, String]]
146
+
147
+ lines.each_with_index do |line, idx|
148
+ next unless comment_line?(line)
149
+
150
+ placeholders.each do |placeholder|
151
+ matches << [idx + 1, line.strip] if line.include?(placeholder)
152
+ end
153
+ end
154
+
155
+ [path, matches] if matches.any?
156
+ end
157
+
158
+ # @private
159
+ # @param [String] line
160
+ # @return [Boolean]
161
+ def comment_line?(line)
162
+ stripped = line.strip
163
+ stripped.start_with?('#') && !stripped.match?(/^#\s*#/)
164
+ end
165
+
166
+ # @private
167
+ # @param [Array<(String, Array<(Integer, String)>)>] results
168
+ # @return [void]
169
+ def report(results)
170
+ total = results.sum { |_path, matches| matches.size }
171
+ puts "Docscribe: Found #{total} placeholder(s) in #{results.size} file(s):"
172
+ puts
173
+
174
+ results.each do |path, matches|
175
+ matches.each do |line_num, text|
176
+ puts " #{path}:#{line_num} #{text}"
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -17,36 +17,39 @@ module Docscribe
17
17
  #
18
18
  # If no relevant CLI override is present, the original config is returned unchanged.
19
19
  #
20
- # @note module_function: when included, also defines #build (instance visibility: private)
20
+ # @note module_function: defines #build (visibility: private)
21
21
  # @param [Docscribe::Config] base base config loaded from YAML/defaults
22
- # @param [Hash] options parsed CLI options
22
+ # @param [Hash<Symbol, Object>] options parsed CLI options
23
23
  # @return [Docscribe::Config] merged effective config
24
24
  def build(base, options)
25
25
  return base unless needs_override?(options)
26
26
 
27
27
  raw = Marshal.load(Marshal.dump(base.raw))
28
28
  apply_filter_overrides(raw, options)
29
- apply_rbs_overrides(raw, options) if options[:rbs] || options[:rbs_collection] || options[:sig_dirs].any?
30
- apply_sorbet_overrides(raw, options) if options[:sorbet] || options[:rbi_dirs].any?
31
- Docscribe::Config.new(raw)
29
+ apply_rbs_overrides(raw, options) if rbs_overrides?(options)
30
+ apply_sorbet_overrides(raw, options) if sorbet_overrides?(options)
31
+ apply_output_overrides(raw, options)
32
+ conf = Docscribe::Config.new(raw)
33
+ warn_missing_rbs_collection(conf, options)
34
+ conf
32
35
  end
33
36
 
34
37
  # Whether any CLI override is present.
35
38
  #
36
- # @note module_function: when included, also defines # (instance visibility: private)
37
- # @private
38
- # @param [Hash] options parsed CLI options
39
+ # @note module_function: defines #needs_override? (visibility: private)
40
+ # @param [Hash<Symbol, Object>] options parsed CLI options
39
41
  # @return [Boolean]
40
42
  def needs_override?(options)
41
43
  filter_overrides?(options) ||
42
44
  rbs_overrides?(options) ||
43
- sorbet_overrides?(options)
45
+ sorbet_overrides?(options) ||
46
+ output_overrides?(options)
44
47
  end
45
48
 
46
49
  # Whether any method or file filter CLI options were provided.
47
50
  #
48
- # @note module_function: when included, also defines #filter_overrides? (instance visibility: private)
49
- # @param [Hash] options parsed CLI options
51
+ # @note module_function: defines #filter_overrides? (visibility: private)
52
+ # @param [Hash<Symbol, Object>] options parsed CLI options
50
53
  # @return [Boolean]
51
54
  def filter_overrides?(options)
52
55
  options[:include].any? ||
@@ -55,10 +58,21 @@ module Docscribe
55
58
  options[:exclude_file].any?
56
59
  end
57
60
 
61
+ # Apply method and file filter overrides to the raw config.
62
+ #
63
+ # @note module_function: defines #apply_filter_overrides (visibility: private)
64
+ # @param [Hash<String, Object>] raw raw config hash
65
+ # @param [Hash<Symbol, Object>] options parsed CLI options
66
+ # @return [void]
67
+ def apply_filter_overrides(raw, options)
68
+ apply_method_filters(raw, options)
69
+ apply_file_filters(raw, options)
70
+ end
71
+
58
72
  # Whether any RBS-related CLI options were provided.
59
73
  #
60
- # @note module_function: when included, also defines #rbs_overrides? (instance visibility: private)
61
- # @param [Hash] options parsed CLI options
74
+ # @note module_function: defines #rbs_overrides? (visibility: private)
75
+ # @param [Hash<Symbol, Object>] options parsed CLI options
62
76
  # @return [Boolean]
63
77
  def rbs_overrides?(options)
64
78
  options[:rbs] ||
@@ -66,33 +80,11 @@ module Docscribe
66
80
  options[:sig_dirs].any?
67
81
  end
68
82
 
69
- # Whether any Sorbet-related CLI options were provided.
70
- #
71
- # @note module_function: when included, also defines #sorbet_overrides? (instance visibility: private)
72
- # @param [Hash] options parsed CLI options
73
- # @return [Boolean]
74
- def sorbet_overrides?(options)
75
- options[:sorbet] ||
76
- options[:rbi_dirs].any?
77
- end
78
-
79
- # Apply method and file filter overrides to the raw config.
80
- #
81
- # @note module_function: when included, also defines # (instance visibility: private)
82
- # @private
83
- # @param [Hash] raw raw config hash
84
- # @param [Hash] options parsed CLI options
85
- # @return [void]
86
- def apply_filter_overrides(raw, options)
87
- apply_method_filters(raw, options)
88
- apply_file_filters(raw, options)
89
- end
90
-
91
83
  # Merge CLI method include/exclude patterns into the raw config hash.
92
84
  #
93
- # @note module_function: when included, also defines #apply_method_filters (instance visibility: private)
94
- # @param [Hash] raw raw config hash
95
- # @param [Hash] options parsed CLI options
85
+ # @note module_function: defines #apply_method_filters (visibility: private)
86
+ # @param [Hash<String, Object>] raw raw config hash
87
+ # @param [Hash<Symbol, Object>] options parsed CLI options
96
88
  # @return [void]
97
89
  def apply_method_filters(raw, options)
98
90
  raw['filter'] ||= {}
@@ -102,22 +94,27 @@ module Docscribe
102
94
 
103
95
  # Merge CLI file include/exclude patterns into the raw config hash.
104
96
  #
105
- # @note module_function: when included, also defines #apply_file_filters (instance visibility: private)
106
- # @param [Hash] raw raw config hash
107
- # @param [Hash] options parsed CLI options
97
+ # @note module_function: defines #apply_file_filters (visibility: private)
98
+ # @param [Hash<String, Object>] raw raw config hash
99
+ # @param [Hash<Symbol, Object>] options parsed CLI options
108
100
  # @return [void]
109
101
  def apply_file_filters(raw, options)
110
- files = raw['filter']['files'] ||= {} #: Hash[String, untyped]
102
+ files = raw['filter']['files']
103
+ if files.nil?
104
+ files = {} #: Hash[String, untyped]
105
+ raw['filter']['files'] = files
106
+ end
107
+
111
108
  files['include'] = Array(files['include']) + options[:include_file]
112
109
  files['exclude'] = Array(files['exclude']) + options[:exclude_file]
110
+ files
113
111
  end
114
112
 
115
113
  # Apply RBS-related CLI overrides to the raw config.
116
114
  #
117
- # @note module_function: when included, also defines # (instance visibility: private)
118
- # @private
119
- # @param [Hash] raw raw config hash
120
- # @param [Hash] options parsed CLI options
115
+ # @note module_function: defines #apply_rbs_overrides (visibility: private)
116
+ # @param [Hash<String, Object>] raw raw config hash
117
+ # @param [Hash<Symbol, Object>] options parsed CLI options
121
118
  # @return [void]
122
119
  def apply_rbs_overrides(raw, options)
123
120
  raw['rbs'] ||= {}
@@ -129,10 +126,20 @@ module Docscribe
129
126
  apply_rbs_collection(raw)
130
127
  end
131
128
 
129
+ # Whether any Sorbet-related CLI options were provided.
130
+ #
131
+ # @note module_function: defines #sorbet_overrides? (visibility: private)
132
+ # @param [Hash<Symbol, Object>] options parsed CLI options
133
+ # @return [Boolean]
134
+ def sorbet_overrides?(options)
135
+ options[:sorbet] ||
136
+ options[:rbi_dirs].any?
137
+ end
138
+
132
139
  # Resolve and apply the RBS collection path into the raw config hash.
133
140
  #
134
- # @note module_function: when included, also defines #apply_rbs_collection (instance visibility: private)
135
- # @param [Hash] raw raw config hash
141
+ # @note module_function: defines #apply_rbs_collection (visibility: private)
142
+ # @param [Hash<String, Object>] raw raw config hash
136
143
  # @return [void]
137
144
  def apply_rbs_collection(raw)
138
145
  require 'docscribe/types/rbs/collection_loader'
@@ -140,17 +147,16 @@ module Docscribe
140
147
  if collection_path
141
148
  raw['rbs']['collection_dirs'] = Array(raw['rbs']['collection_dirs']) + [collection_path]
142
149
  else
143
- warn 'Docscribe: rbs_collection.lock.yaml not found or collection not installed. ' \
150
+ warn 'Docscribe: rbs_collection.lock.yaml not found. ' \
144
151
  'Run `bundle exec rbs collection install` first.'
145
152
  end
146
153
  end
147
154
 
148
155
  # Apply Sorbet-related CLI overrides to the raw config.
149
156
  #
150
- # @note module_function: when included, also defines # (instance visibility: private)
151
- # @private
152
- # @param [Hash] raw raw config hash
153
- # @param [Hash] options parsed CLI options
157
+ # @note module_function: defines #apply_sorbet_overrides (visibility: private)
158
+ # @param [Hash<String, Object>] raw raw config hash
159
+ # @param [Hash<Symbol, Object>] options parsed CLI options
154
160
  # @return [void]
155
161
  def apply_sorbet_overrides(raw, options)
156
162
  raw['sorbet'] ||= {}
@@ -159,6 +165,54 @@ module Docscribe
159
165
 
160
166
  raw['sorbet']['rbi_dirs'] = Array(raw['sorbet']['rbi_dirs']) + options[:rbi_dirs]
161
167
  end
168
+
169
+ # Whether any output-related CLI options were provided.
170
+ #
171
+ # @note module_function: defines #output_overrides? (visibility: private)
172
+ # @param [Hash<Symbol, Object>] options parsed CLI options
173
+ # @return [Boolean]
174
+ def output_overrides?(options)
175
+ !!options[:keep_descriptions] || !!options[:no_boilerplate]
176
+ end
177
+
178
+ # Apply output-related CLI overrides to the raw config.
179
+ #
180
+ # Currently handles:
181
+ # - `keep_descriptions` -> raw['keep_descriptions']
182
+ # - `no_boilerplate` -> raw['emit']['include_default_message'] and
183
+ # raw['emit']['include_param_documentation'] = false
184
+ #
185
+ # @note module_function: defines #apply_output_overrides (visibility: private)
186
+ # @param [Hash<String, Object>] raw raw config hash
187
+ # @param [Hash<Symbol, Object>] options parsed CLI options
188
+ # @return [void]
189
+ def apply_output_overrides(raw, options)
190
+ return unless options[:keep_descriptions] || options[:no_boilerplate]
191
+
192
+ raw['keep_descriptions'] = true if options[:keep_descriptions]
193
+ raw['emit'] ||= {}
194
+ raw['emit']['include_default_message'] = false if options[:no_boilerplate]
195
+ raw['emit']['include_param_documentation'] = false if options[:no_boilerplate]
196
+ end
197
+
198
+ # Warn when rbs_collection.lock.yaml exists but --rbs-collection was not passed.
199
+ #
200
+ # The warning can be suppressed by setting `rbs.warn_missing_collection: false`
201
+ # in the project's `docscribe.yml`.
202
+ #
203
+ # @note module_function: defines #warn_missing_rbs_collection (visibility: private)
204
+ # @param [Docscribe::Config] conf effective config
205
+ # @param [Hash<Symbol, Object>] options parsed CLI options
206
+ # @return [void]
207
+ def warn_missing_rbs_collection(conf, options)
208
+ return if options[:rbs_collection]
209
+ return unless conf.rbs_warn_missing_collection?
210
+ return unless File.exist?('rbs_collection.lock.yaml')
211
+
212
+ warn 'Docscribe: rbs_collection.lock.yaml found but --rbs-collection not set. ' \
213
+ 'Pass --rbs-collection or set `rbs.collection: true` in docscribe.yml to enable RBS collection. ' \
214
+ 'Set `rbs.warn_missing_collection: false` to suppress this warning.'
215
+ end
162
216
  end
163
217
  end
164
218
  end