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,294 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'docscribe/version'
5
+
6
+ module Docscribe
7
+ module CLI
8
+ module Formatters
9
+ # Output formatter producing RuboCop-compatible JSON.
10
+ #
11
+ # stdout: complete JSON document with all findings.
12
+ # stderr: progress markers only (same as text mode).
13
+ class Json
14
+ SEVERITY_MAP = {
15
+ missing_param: 'convention',
16
+ missing_return: 'convention',
17
+ missing_raise: 'convention',
18
+ missing_visibility: 'convention',
19
+ missing_module_function_note: 'convention',
20
+ insert_full_doc_block: 'convention',
21
+ unsorted_tags: 'convention',
22
+ updated_param: 'warning',
23
+ updated_return: 'warning'
24
+ }.freeze
25
+
26
+ COP_NAME_MAP = {
27
+ missing_param: 'Docscribe/MissingParam',
28
+ missing_return: 'Docscribe/MissingReturn',
29
+ missing_raise: 'Docscribe/MissingRaise',
30
+ missing_visibility: 'Docscribe/MissingVisibility',
31
+ missing_module_function_note: 'Docscribe/MissingModuleFunctionNote',
32
+ insert_full_doc_block: 'Docscribe/MissingDocBlock',
33
+ unsorted_tags: 'Docscribe/UnsortedTags',
34
+ updated_param: 'Docscribe/UpdatedParam',
35
+ updated_return: 'Docscribe/UpdatedReturn'
36
+ }.freeze
37
+
38
+ # Output JSON check summary.
39
+ #
40
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
41
+ # @param [Docscribe::CLI::Formatters::opts] options runtime options hash
42
+ # @return [void]
43
+ def format_check_summary(state:, options:)
44
+ puts JSON.generate(build_document(state, options))
45
+ end
46
+
47
+ # Output JSON write summary.
48
+ #
49
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
50
+ # @param [Docscribe::CLI::Formatters::opts] options runtime options hash
51
+ # @return [void]
52
+ def format_write_summary(state:, options:)
53
+ puts JSON.generate(build_document(state, options))
54
+ end
55
+
56
+ private
57
+
58
+ # Build full JSON document.
59
+ #
60
+ # @private
61
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
62
+ # @param [Docscribe::CLI::Formatters::opts] _options runtime options hash
63
+ # @return [Hash<Symbol, Object>]
64
+ def build_document(state, _options)
65
+ document_hash(build_files(state), state)
66
+ end
67
+
68
+ # Build document hash structure.
69
+ #
70
+ # @private
71
+ # @param [Array<Hash<Symbol, Object>>] files files offenses array
72
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
73
+ # @return [Hash<Symbol, Object>]
74
+ def document_hash(files, state)
75
+ {
76
+ metadata: metadata_hash,
77
+ files: files,
78
+ summary: summary_hash(files, state)
79
+ }
80
+ end
81
+
82
+ # Build tool metadata hash.
83
+ #
84
+ # @private
85
+ # @return [Hash<Symbol, String>]
86
+ def metadata_hash
87
+ {
88
+ docscribe_version: Docscribe::VERSION,
89
+ ruby_version: RUBY_VERSION
90
+ }
91
+ end
92
+
93
+ # Build summary statistics hash.
94
+ #
95
+ # @private
96
+ # @param [Array<Hash<Symbol, Object>>] files files offenses array
97
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
98
+ # @return [Hash<Symbol, Integer>]
99
+ def summary_hash(files, state)
100
+ {
101
+ offense_count: files.sum { |f| f[:offenses].size },
102
+ target_file_count: files.size,
103
+ inspected_file_count: inspected_count(state),
104
+ error_count: state[:error_paths].size
105
+ }
106
+ end
107
+
108
+ # Build files array from state.
109
+ #
110
+ # @private
111
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
112
+ # @return [Array<Hash<Symbol, Object>>]
113
+ def build_files(state)
114
+ files = [] #: Array[Hash[untyped, untyped]]
115
+
116
+ append_check_files(state, files) if state[:fail_paths].any? || state[:type_mismatch_paths].any?
117
+ append_corrected_files(state, files) if state[:corrected_paths].any?
118
+ append_error_files(state, files) if state[:error_paths].any?
119
+
120
+ files
121
+ end
122
+
123
+ # Append check file entries.
124
+ #
125
+ # @private
126
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
127
+ # @param [Array<Hash<Symbol, Object>>] files files offenses array
128
+ # @return [void]
129
+ def append_check_files(state, files)
130
+ state[:fail_paths].each do |path|
131
+ files << file_entry(path, state[:fail_changes][path] || [])
132
+ end
133
+
134
+ state[:type_mismatch_paths].each do |path|
135
+ files << file_entry(path, state[:type_mismatch_changes][path] || [], severity: 'warning')
136
+ end
137
+ end
138
+
139
+ # Append corrected file entries.
140
+ #
141
+ # @private
142
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
143
+ # @param [Array<Hash<Symbol, Object>>] files files offenses array
144
+ # @return [void]
145
+ def append_corrected_files(state, files)
146
+ state[:corrected_paths].each do |path|
147
+ merge_or_append(files, path, state[:corrected_changes][path] || [])
148
+ end
149
+ end
150
+
151
+ # Append error file entries.
152
+ #
153
+ # @private
154
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
155
+ # @param [Array<Hash<Symbol, Object>>] files files offenses array
156
+ # @return [void]
157
+ def append_error_files(state, files)
158
+ state[:error_paths].each do |path|
159
+ merge_or_append(files, path, [error_offense(state, path)])
160
+ end
161
+ end
162
+
163
+ # Merge or append file offenses.
164
+ #
165
+ # @private
166
+ # @param [Array<Hash<Symbol, Object>>] files files offenses array
167
+ # @param [String] path file path string
168
+ # @param [Array<Hash<Symbol, Object>>] offenses offense objects array
169
+ # @return [void]
170
+ def merge_or_append(files, path, offenses)
171
+ existing = files.find { |f| f[:path] == path }
172
+
173
+ if existing
174
+ existing[:offenses].concat(offenses)
175
+ else
176
+ files << { path: path, offenses: offenses }
177
+ end
178
+ end
179
+
180
+ # Build single file entry hash.
181
+ #
182
+ # @private
183
+ # @param [String] path file path string
184
+ # @param [Array<Docscribe::CLI::Formatters::change>] changes changes info array
185
+ # @param [String?] severity offense severity level
186
+ # @return [Hash<Symbol, Object>]
187
+ def file_entry(path, changes, severity: nil)
188
+ { path: path, offenses: build_offenses(changes, severity: severity) }
189
+ end
190
+
191
+ # Build error offense entry.
192
+ #
193
+ # @private
194
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
195
+ # @param [String] path file path string
196
+ # @return [Hash<Symbol, Object>]
197
+ def error_offense(state, path)
198
+ error_offense_hash(state[:error_messages][path] || 'Unknown error')
199
+ end
200
+
201
+ # Format error offense hash.
202
+ #
203
+ # @private
204
+ # @param [String] message error message string
205
+ # @return [Hash<Symbol, Object>]
206
+ def error_offense_hash(message)
207
+ { severity: 'fatal', cop_name: 'Docscribe/ProcessingError', message: message,
208
+ corrected: false, correctable: false, location: default_location }
209
+ end
210
+
211
+ # Default location hash value.
212
+ #
213
+ # @private
214
+ # @return [Hash<Symbol, Integer>]
215
+ def default_location
216
+ { start_line: 1, start_column: 1, last_line: 1, last_column: 1 }
217
+ end
218
+
219
+ # Build offense array from changes.
220
+ #
221
+ # @private
222
+ # @param [Array<Docscribe::CLI::Formatters::change>] changes changes info array
223
+ # @param [String?] severity offense severity level
224
+ # @return [Array<Hash<Symbol, Object>>]
225
+ def build_offenses(changes, severity: nil)
226
+ changes.map { |change| build_offense(change, severity) }
227
+ end
228
+
229
+ # Build single offense hash.
230
+ #
231
+ # @private
232
+ # @param [Docscribe::CLI::Formatters::change] change change info hash
233
+ # @param [String?] severity offense severity level
234
+ # @return [Hash<Symbol, Object>]
235
+ def build_offense(change, severity)
236
+ {
237
+ severity: severity || SEVERITY_MAP[change[:type]] || 'convention',
238
+ cop_name: COP_NAME_MAP[change[:type]] || cop_name_fallback(change),
239
+ message: build_message(change),
240
+ corrected: false,
241
+ correctable: true,
242
+ location: location_for(change)
243
+ }
244
+ end
245
+
246
+ # Build location hash from change.
247
+ #
248
+ # @private
249
+ # @param [Docscribe::CLI::Formatters::change] change change info hash
250
+ # @return [Hash<Symbol, Integer>]
251
+ def location_for(change)
252
+ line = change[:line] || 1
253
+ { start_line: line, start_column: 1, last_line: line, last_column: 1 }
254
+ end
255
+
256
+ # Fallback cop name from type.
257
+ #
258
+ # @private
259
+ # @param [Docscribe::CLI::Formatters::change] change change info hash
260
+ # @return [String]
261
+ def cop_name_fallback(change)
262
+ name = change[:type].to_s.tr('_', '_').capitalize
263
+ "Docscribe/#{name}"
264
+ end
265
+
266
+ # Build human-readable message.
267
+ #
268
+ # @private
269
+ # @param [Docscribe::CLI::Formatters::change] change change info hash
270
+ # @return [String]
271
+ def build_message(change)
272
+ method = change[:method] ? " for #{change[:method]}" : ''
273
+ line = change[:line] ? " at line #{change[:line]}" : ''
274
+
275
+ return "unsorted tags#{line}" if change[:type] == :unsorted_tags
276
+
277
+ msg = change[:message] || change[:type].to_s.tr('_', ' ')
278
+ "#{msg}#{method}#{line}"
279
+ end
280
+
281
+ # Count inspected file total.
282
+ #
283
+ # @private
284
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
285
+ # @return [Integer]
286
+ def inspected_count(state)
287
+ total = state[:checked_ok] + state[:checked_fail] + state[:type_mismatch_paths].size
288
+ total = state[:corrected] if total.zero? && state[:corrected].positive?
289
+ total + state[:error_paths].size
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'docscribe/version'
5
+
6
+ module Docscribe
7
+ module CLI
8
+ module Formatters
9
+ # Output formatter producing SARIF 2.1 JSON.
10
+ #
11
+ # SARIF (Static Analysis Results Interchange Format) is a standard
12
+ # format for static analysis tools. This formatter produces output
13
+ # compatible with GitHub Code Scanning, VS Code SARIF viewer, etc.
14
+ #
15
+ # stdout: complete SARIF 2.1 document with all findings.
16
+ # stderr: progress markers only (same as text mode).
17
+ class Sarif
18
+ SEVERITY_MAP = {
19
+ missing_param: 'note',
20
+ missing_return: 'note',
21
+ missing_raise: 'note',
22
+ missing_visibility: 'note',
23
+ missing_module_function_note: 'note',
24
+ insert_full_doc_block: 'note',
25
+ unsorted_tags: 'note',
26
+ updated_param: 'warning',
27
+ updated_return: 'warning'
28
+ }.freeze
29
+
30
+ COP_NAME_MAP = {
31
+ missing_param: 'Docscribe/MissingParam',
32
+ missing_return: 'Docscribe/MissingReturn',
33
+ missing_raise: 'Docscribe/MissingRaise',
34
+ missing_visibility: 'Docscribe/MissingVisibility',
35
+ missing_module_function_note: 'Docscribe/MissingModuleFunctionNote',
36
+ insert_full_doc_block: 'Docscribe/MissingDocBlock',
37
+ unsorted_tags: 'Docscribe/UnsortedTags',
38
+ updated_param: 'Docscribe/UpdatedParam',
39
+ updated_return: 'Docscribe/UpdatedReturn'
40
+ }.freeze
41
+
42
+ SARIF_SCHEMA = 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/' \
43
+ 'master/Schemata/sarif-schema-2.1.0.json'
44
+
45
+ # @param [Docscribe::CLI::Formatters::state] state
46
+ # @param [Docscribe::CLI::Formatters::opts] options
47
+ # @return [void]
48
+ def format_check_summary(state:, options:)
49
+ puts JSON.generate(build_sarif_document(state, options[:format]))
50
+ end
51
+
52
+ # @param [Docscribe::CLI::Formatters::state] state
53
+ # @param [Docscribe::CLI::Formatters::opts] options
54
+ # @return [void]
55
+ def format_write_summary(state:, options:)
56
+ puts JSON.generate(build_sarif_document(state, options[:format]))
57
+ end
58
+
59
+ private
60
+
61
+ # @private
62
+ # @param [Docscribe::CLI::Formatters::state] state
63
+ # @param [Object] _format
64
+ # @return [Hash<String, Symbol, Object>]
65
+ def build_sarif_document(state, _format)
66
+ {
67
+ '$schema' => SARIF_SCHEMA,
68
+ version: '2.1.0',
69
+ runs: [build_run(state)]
70
+ }
71
+ end
72
+
73
+ # @private
74
+ # @param [Docscribe::CLI::Formatters::state] state
75
+ # @return [Hash<Symbol, Object>]
76
+ def build_run(state)
77
+ {
78
+ tool: build_tool,
79
+ results: build_results(state),
80
+ invocations: [build_invocation(state)]
81
+ }
82
+ end
83
+
84
+ # @private
85
+ # @return [Hash<Symbol, Object>]
86
+ def build_tool
87
+ {
88
+ driver: {
89
+ name: 'docscribe',
90
+ version: Docscribe::VERSION,
91
+ informationUri: 'https://github.com/unurgunite/docscribe'
92
+ }
93
+ }
94
+ end
95
+
96
+ # @private
97
+ # @param [Docscribe::CLI::Formatters::state] state
98
+ # @return [Array<Hash<Symbol, Object>>]
99
+ def build_results(state)
100
+ results = [] #: Array[Hash[Symbol, top]]
101
+
102
+ append_check_results(state, results)
103
+ append_corrected_results(state, results)
104
+ append_error_results(state, results)
105
+
106
+ results
107
+ end
108
+
109
+ # @private
110
+ # @param [Docscribe::CLI::Formatters::state] state
111
+ # @param [Array<Hash<Symbol, Object>>] results
112
+ # @return [void]
113
+ def append_check_results(state, results)
114
+ append_changes(state[:fail_paths], state[:fail_changes], results)
115
+ append_changes(state[:type_mismatch_paths], state[:type_mismatch_changes], results, level: 'warning')
116
+ end
117
+
118
+ # @private
119
+ # @param [Array<String>] paths
120
+ # @param [Hash<String, Array<Docscribe::CLI::Formatters::change>>] changes_map
121
+ # @param [Array<Hash<Symbol, Object>>] results
122
+ # @param [String?] level
123
+ # @return [void]
124
+ def append_changes(paths, changes_map, results, level: nil)
125
+ paths.each do |path|
126
+ (changes_map[path] || []).each do |change|
127
+ results << build_result(change, path, level: level)
128
+ end
129
+ end
130
+ end
131
+
132
+ # @private
133
+ # @param [Docscribe::CLI::Formatters::state] state
134
+ # @param [Array<Hash<Symbol, Object>>] results
135
+ # @return [void]
136
+ def append_corrected_results(state, results)
137
+ state[:corrected_paths].each do |path|
138
+ changes = state[:corrected_changes][path] || []
139
+ changes.each do |change|
140
+ results << build_result(change, path)
141
+ end
142
+ end
143
+ end
144
+
145
+ # @private
146
+ # @param [Docscribe::CLI::Formatters::state] state
147
+ # @param [Array<Hash<Symbol, Object>>] results
148
+ # @return [void]
149
+ def append_error_results(state, results)
150
+ state[:error_paths].each do |path|
151
+ msg = state[:error_messages][path] || 'Unknown error'
152
+ results << build_error_result(msg, path)
153
+ end
154
+ end
155
+
156
+ # @private
157
+ # @param [Docscribe::CLI::Formatters::change] change
158
+ # @param [String] path
159
+ # @param [String?] level
160
+ # @return [Hash<Symbol, Object>]
161
+ def build_result(change, path, level: nil)
162
+ {
163
+ ruleId: cop_name_for(change),
164
+ level: level || SEVERITY_MAP[change[:type]] || 'note',
165
+ message: { text: message_for(change) },
166
+ locations: [location(path, change[:line] || 1)]
167
+ }
168
+ end
169
+
170
+ # @private
171
+ # @param [String] message
172
+ # @param [String] path
173
+ # @return [Hash<Symbol, Object>]
174
+ def build_error_result(message, path)
175
+ {
176
+ ruleId: 'Docscribe/ProcessingError',
177
+ level: 'error',
178
+ message: { text: message },
179
+ locations: [location(path, 1)]
180
+ }
181
+ end
182
+
183
+ # @private
184
+ # @param [String] path
185
+ # @param [Integer] line
186
+ # @return [Hash<Symbol, Object>]
187
+ def location(path, line)
188
+ {
189
+ physicalLocation: {
190
+ artifactLocation: { uri: path },
191
+ region: { startLine: line }
192
+ }
193
+ }
194
+ end
195
+
196
+ # @private
197
+ # @param [Docscribe::CLI::Formatters::change] change
198
+ # @return [String]
199
+ def cop_name_for(change)
200
+ COP_NAME_MAP[change[:type]] || fallback_cop_name(change)
201
+ end
202
+
203
+ # @private
204
+ # @param [Docscribe::CLI::Formatters::change] change
205
+ # @return [String]
206
+ def fallback_cop_name(change)
207
+ name = change[:type].to_s.tr('_', ' ').split.map(&:capitalize).join
208
+ "Docscribe/#{name}"
209
+ end
210
+
211
+ # @private
212
+ # @param [Docscribe::CLI::Formatters::change] change
213
+ # @return [String]
214
+ def message_for(change)
215
+ method = change[:method] ? " for #{change[:method]}" : ''
216
+ line = change[:line] ? " at line #{change[:line]}" : ''
217
+
218
+ return "unsorted tags#{line}" if change[:type] == :unsorted_tags
219
+
220
+ msg = change[:message] || change[:type].to_s.tr('_', ' ')
221
+ "#{msg}#{method}#{line}"
222
+ end
223
+
224
+ # @private
225
+ # @param [Docscribe::CLI::Formatters::state] state
226
+ # @return [Hash<Symbol, Object>]
227
+ def build_invocation(state)
228
+ {
229
+ executionSuccessful: !state[:had_errors]
230
+ }
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end