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
@@ -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
@@ -4,6 +4,7 @@ require 'docscribe/config'
4
4
 
5
5
  module Docscribe
6
6
  module CLI
7
+ # Build and override effective config from CLI flags.
7
8
  module ConfigBuilder
8
9
  module_function
9
10
 
@@ -16,58 +17,201 @@ module Docscribe
16
17
  #
17
18
  # If no relevant CLI override is present, the original config is returned unchanged.
18
19
  #
19
- # @note module_function: when included, also defines #build (instance visibility: private)
20
+ # @note module_function: defines #build (visibility: private)
20
21
  # @param [Docscribe::Config] base base config loaded from YAML/defaults
21
- # @param [Hash] options parsed CLI options
22
+ # @param [Hash<Symbol, Object>] options parsed CLI options
22
23
  # @return [Docscribe::Config] merged effective config
23
24
  def build(base, options)
24
- needs_override =
25
- options[:include].any? ||
25
+ return base unless needs_override?(options)
26
+
27
+ raw = Marshal.load(Marshal.dump(base.raw))
28
+ apply_filter_overrides(raw, options)
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
35
+ end
36
+
37
+ # Whether any CLI override is present.
38
+ #
39
+ # @note module_function: defines #needs_override? (visibility: private)
40
+ # @param [Hash<Symbol, Object>] options parsed CLI options
41
+ # @return [Boolean]
42
+ def needs_override?(options)
43
+ filter_overrides?(options) ||
44
+ rbs_overrides?(options) ||
45
+ sorbet_overrides?(options) ||
46
+ output_overrides?(options)
47
+ end
48
+
49
+ # Whether any method or file filter CLI options were provided.
50
+ #
51
+ # @note module_function: defines #filter_overrides? (visibility: private)
52
+ # @param [Hash<Symbol, Object>] options parsed CLI options
53
+ # @return [Boolean]
54
+ def filter_overrides?(options)
55
+ options[:include].any? ||
26
56
  options[:exclude].any? ||
27
57
  options[:include_file].any? ||
28
- options[:exclude_file].any? ||
29
- options[:rbs] ||
30
- options[:rbs_collection] ||
31
- options[:sig_dirs].any? ||
32
- options[:sorbet] ||
33
- options[:rbi_dirs].any?
58
+ options[:exclude_file].any?
59
+ end
34
60
 
35
- return base unless needs_override
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
36
71
 
37
- raw = Marshal.load(Marshal.dump(base.raw))
72
+ # Whether any RBS-related CLI options were provided.
73
+ #
74
+ # @note module_function: defines #rbs_overrides? (visibility: private)
75
+ # @param [Hash<Symbol, Object>] options parsed CLI options
76
+ # @return [Boolean]
77
+ def rbs_overrides?(options)
78
+ options[:rbs] ||
79
+ options[:rbs_collection] ||
80
+ options[:sig_dirs].any?
81
+ end
38
82
 
83
+ # Merge CLI method include/exclude patterns into the raw config hash.
84
+ #
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
88
+ # @return [void]
89
+ def apply_method_filters(raw, options)
39
90
  raw['filter'] ||= {}
40
91
  raw['filter']['include'] = Array(raw['filter']['include']) + options[:include]
41
92
  raw['filter']['exclude'] = Array(raw['filter']['exclude']) + options[:exclude]
93
+ end
42
94
 
43
- raw['filter']['files'] ||= {}
44
- raw['filter']['files']['include'] = Array(raw['filter']['files']['include']) + options[:include_file]
45
- raw['filter']['files']['exclude'] = Array(raw['filter']['files']['exclude']) + options[:exclude_file]
46
-
47
- if options[:rbs] || options[:rbs_collection] || options[:sig_dirs].any?
48
- raw['rbs'] ||= {}
49
- raw['rbs']['enabled'] = true
50
- raw['rbs']['sig_dirs'] = Array(raw['rbs']['sig_dirs']) + options[:sig_dirs] if options[:sig_dirs].any?
51
-
52
- if options[:rbs_collection]
53
- require 'docscribe/types/rbs/collection_loader'
54
- collection_path = Docscribe::Types::RBS::CollectionLoader.resolve
55
- if collection_path
56
- raw['rbs']['collection_dirs'] = Array(raw['rbs']['collection_dirs']) + [collection_path]
57
- else
58
- warn 'Docscribe: rbs_collection.lock.yaml not found or collection not installed. ' \
59
- 'Run `bundle exec rbs collection install` first.'
60
- end
61
- end
95
+ # Merge CLI file include/exclude patterns into the raw config hash.
96
+ #
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
100
+ # @return [void]
101
+ def apply_file_filters(raw, options)
102
+ files = raw['filter']['files']
103
+ if files.nil?
104
+ files = {} #: Hash[String, untyped]
105
+ raw['filter']['files'] = files
62
106
  end
63
107
 
64
- if options[:sorbet] || options[:rbi_dirs].any?
65
- raw['sorbet'] ||= {}
66
- raw['sorbet']['enabled'] = true
67
- raw['sorbet']['rbi_dirs'] = Array(raw['sorbet']['rbi_dirs']) + options[:rbi_dirs] if options[:rbi_dirs].any?
108
+ files['include'] = Array(files['include']) + options[:include_file]
109
+ files['exclude'] = Array(files['exclude']) + options[:exclude_file]
110
+ files
111
+ end
112
+
113
+ # Apply RBS-related CLI overrides to the raw config.
114
+ #
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
118
+ # @return [void]
119
+ def apply_rbs_overrides(raw, options)
120
+ raw['rbs'] ||= {}
121
+ raw['rbs']['enabled'] = true
122
+ raw['rbs']['sig_dirs'] = Array(raw['rbs']['sig_dirs']) + options[:sig_dirs] if options[:sig_dirs].any?
123
+
124
+ return unless options[:rbs_collection]
125
+
126
+ apply_rbs_collection(raw)
127
+ end
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
+
139
+ # Resolve and apply the RBS collection path into the raw config hash.
140
+ #
141
+ # @note module_function: defines #apply_rbs_collection (visibility: private)
142
+ # @param [Hash<String, Object>] raw raw config hash
143
+ # @return [void]
144
+ def apply_rbs_collection(raw)
145
+ require 'docscribe/types/rbs/collection_loader'
146
+ collection_path = Docscribe::Types::RBS::CollectionLoader.resolve
147
+ if collection_path
148
+ raw['rbs']['collection_dirs'] = Array(raw['rbs']['collection_dirs']) + [collection_path]
149
+ else
150
+ warn 'Docscribe: rbs_collection.lock.yaml not found. ' \
151
+ 'Run `bundle exec rbs collection install` first.'
68
152
  end
153
+ end
154
+
155
+ # Apply Sorbet-related CLI overrides to the raw config.
156
+ #
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
160
+ # @return [void]
161
+ def apply_sorbet_overrides(raw, options)
162
+ raw['sorbet'] ||= {}
163
+ raw['sorbet']['enabled'] = true
164
+ return unless options[:rbi_dirs].any?
165
+
166
+ raw['sorbet']['rbi_dirs'] = Array(raw['sorbet']['rbi_dirs']) + options[:rbi_dirs]
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')
69
211
 
70
- Docscribe::Config.new(raw)
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.'
71
215
  end
72
216
  end
73
217
  end