docscribe 1.4.1 → 1.4.2

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +149 -0
  3. data/lib/docscribe/cli/config_builder.rb +125 -35
  4. data/lib/docscribe/cli/generate.rb +288 -117
  5. data/lib/docscribe/cli/init.rb +49 -13
  6. data/lib/docscribe/cli/options.rb +302 -127
  7. data/lib/docscribe/cli/run.rb +391 -135
  8. data/lib/docscribe/cli.rb +23 -5
  9. data/lib/docscribe/config/defaults.rb +11 -11
  10. data/lib/docscribe/config/emit.rb +1 -0
  11. data/lib/docscribe/config/filtering.rb +24 -11
  12. data/lib/docscribe/config/loader.rb +7 -4
  13. data/lib/docscribe/config/plugin.rb +1 -0
  14. data/lib/docscribe/config/rbs.rb +31 -22
  15. data/lib/docscribe/config/sorbet.rb +41 -15
  16. data/lib/docscribe/config/sorting.rb +1 -0
  17. data/lib/docscribe/config/template.rb +1 -0
  18. data/lib/docscribe/config/utils.rb +1 -0
  19. data/lib/docscribe/config.rb +1 -0
  20. data/lib/docscribe/infer/constants.rb +15 -0
  21. data/lib/docscribe/infer/literals.rb +43 -25
  22. data/lib/docscribe/infer/names.rb +24 -15
  23. data/lib/docscribe/infer/params.rb +52 -6
  24. data/lib/docscribe/infer/raises.rb +24 -14
  25. data/lib/docscribe/infer/returns.rb +365 -182
  26. data/lib/docscribe/infer.rb +10 -9
  27. data/lib/docscribe/inline_rewriter/collector.rb +766 -375
  28. data/lib/docscribe/inline_rewriter/doc_block.rb +217 -74
  29. data/lib/docscribe/inline_rewriter/doc_builder.rb +1488 -602
  30. data/lib/docscribe/inline_rewriter/source_helpers.rb +100 -52
  31. data/lib/docscribe/inline_rewriter/tag_sorter.rb +109 -48
  32. data/lib/docscribe/inline_rewriter.rb +1009 -595
  33. data/lib/docscribe/plugin/base/collector_plugin.rb +2 -3
  34. data/lib/docscribe/plugin/base/tag_plugin.rb +1 -1
  35. data/lib/docscribe/plugin/registry.rb +34 -7
  36. data/lib/docscribe/plugin.rb +48 -17
  37. data/lib/docscribe/types/rbs/collection_loader.rb +0 -1
  38. data/lib/docscribe/types/rbs/provider.rb +75 -26
  39. data/lib/docscribe/types/rbs/type_formatter.rb +127 -59
  40. data/lib/docscribe/types/sorbet/base_provider.rb +31 -12
  41. data/lib/docscribe/version.rb +1 -1
  42. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e59e0f95a3faa67d4d9d7829ed32e2acd30f3fa5b3236f93d4b5f8bb63ae7616
4
- data.tar.gz: cae706be421e11e31c6124a5e918c407b09a0cdd4435c2ad2d427b63ea4a9f80
3
+ metadata.gz: 5b6ff6ead507182a9530fa2f2cc690d435448284f9212f4fbeea3950dddf40c3
4
+ data.tar.gz: 17e3285caddd09bda308e89f5cb1eb4579d8f96855177f81dbe459b7ad84e9d4
5
5
  SHA512:
6
- metadata.gz: c864edd9a99ad81667030ccfc13936ebb8d13948528bc288235aa2e4ff0de349b16eb3bf3f2b9606d33c8af9129357639a29db319e0c62bddd6ec1df997a7fea
7
- data.tar.gz: 4c62242b2455c5a6117ba4e1f02335b911b4894c6f15aa852c704d9583cd89ca6e0439fcc4cee6d6b3decb6e33afb75c90effa71a5a24c9e39f3fe56e0245a35
6
+ metadata.gz: d7a764fb2d297c1449995727c7d79704db934df7a52f2f2475d21fc6b29c60ea8908fc7ebb560592a3ad6de4d1384937ce75836cbce3e7899eef5918f05e6ab9
7
+ data.tar.gz: 8ab0b426a364eb5dfe823370148b2e2e9e93ad1f2e788bda9143533aa139821c2d159351255a86d8dd739b647572168e4c539e5764e6d562cc6cf7ea9b79acd2
data/README.md CHANGED
@@ -44,6 +44,8 @@ Common workflows:
44
44
  * [Contents](#contents)
45
45
  * [Installation](#installation)
46
46
  * [Quick start](#quick-start)
47
+ * [Architecture](#architecture)
48
+ * [Data flow](#data-flow)
47
49
  * [CLI](#cli)
48
50
  * [Options](#options)
49
51
  * [Examples](#examples)
@@ -86,6 +88,8 @@ Common workflows:
86
88
  * [Generate a plugin skeleton](#generate-a-plugin-skeleton)
87
89
  * [CI integration](#ci-integration)
88
90
  * [Comparison to YARD's parser](#comparison-to-yards-parser)
91
+ * [Mermaid Architecture Reference](#mermaid-architecture-reference)
92
+ * [Data flow](#data-flow)
89
93
  * [Limitations](#limitations)
90
94
  * [Roadmap](#roadmap)
91
95
  * [Contributing](#contributing)
@@ -204,6 +208,151 @@ end
204
208
  > - Class methods show with a dot (`+Demo.bump+`, `+Demo.internal+`).
205
209
  > - Methods inside `class << self` under `private` are marked `@private`.
206
210
 
211
+ ## Architecture
212
+
213
+ Docscribe is organized into seven subsystems. The CLI layer receives user input and orchestrates configuration loading,
214
+ then delegates to the core engine which parses source code, collects methods (using an AST walker), builds YARD doc
215
+ lines — combining heuristic type inference, external RBS/Sorbet signatures, and plugin output — and finally rewrites the
216
+ source via a strategy (safe merge or aggressive replace).
217
+
218
+ ```mermaid
219
+ flowchart TB
220
+ subgraph CLI["CLI Layer"]
221
+ Exe["exe/docscribe\nEntry point"]
222
+ Run["CLI::Run\nMain execution\n· expand paths\n· iterate files\n· report results"]
223
+ Options["CLI::Options\nARGV parsing\n(mode, strategy,\nfilters, flags)"]
224
+ InitCmd["CLI::Init\ndocscribe init\nGenerate config"]
225
+ GenCmd["CLI::Generate\ndocscribe generate\nScaffold plugins"]
226
+ ConfigBuilder["CLI::ConfigBuilder\nApply CLI overrides\nto config"]
227
+ end
228
+
229
+ subgraph Config["Configuration"]
230
+ ConfigClass["Config\nCentral config object\n· raw hash\n· query methods"]
231
+ Defaults["config/defaults.rb\nDEFAULT hash"]
232
+ Loader["config/loader.rb\nYAML loading\n+ deep merge"]
233
+ Emit["config/emit.rb\nEmission toggles\n(header, tags, etc.)"]
234
+ Filtering["config/filtering.rb\nFile/method\ninclude/exclude"]
235
+ RBSConfig["config/rbs.rb\nRBS provider\nfactory"]
236
+ SorbetConfig["config/sorbet.rb\nSorbet provider\nchain factory"]
237
+ PluginConfig["config/plugin.rb\nPlugin loading\nfrom YAML"]
238
+ end
239
+
240
+ subgraph Parsing["Parsing"]
241
+ ParsingModule["Parsing\nBackend selection\n(:parser / :prism)"]
242
+ ParserGem["Parser gem\n(whitequark/parser)"]
243
+ Prism["Prism translator\n(Ruby 3.4+)"]
244
+ end
245
+
246
+ subgraph Core["Core Engine"]
247
+ InlineRewriter["InlineRewriter\n· parse → collect\n· deduplicate → dispatch\n· rewrite"]
248
+ Collector["Collector\n< Parser::AST::Processor\nAST walker\n· find methods/attrs\n· track visibility\n· track containers"]
249
+ DocBuilder["DocBuilder\nGenerate YARD doc lines\n· combine inference\n· external signatures\n· plugin tags"]
250
+ DocBlock["DocBlock\nSafe strategy:\nparse → merge → sort\nexisting doc blocks"]
251
+ SourceHelpers["SourceHelpers\nPosition/range\nutilities"]
252
+ end
253
+
254
+ subgraph Infer["Inference Engine"]
255
+ InferModule["Infer\nEntry point"]
256
+ Params["Infer::Params\nParameter type\nfrom name + default"]
257
+ Returns["Infer::Returns\nReturn type\nfrom method body"]
258
+ Raises["Infer::Raises\n@raise tags\nfrom raise/rescue"]
259
+ Literals["Infer::Literals\nAST literal →\ntype string"]
260
+ Names["Infer::Names\n:const node →\nFQN string"]
261
+ ASTWalk["Infer::ASTWalk\nRecursive DFS\nAST traversal"]
262
+ end
263
+
264
+ subgraph Plugins["Plugin System"]
265
+ PluginModule["Plugin\nTag/Collector\ndispatch"]
266
+ Registry["Plugin::Registry\nGlobal registry\n· register → route\n· tag_entries\n· collector_entries"]
267
+ TagPlugin["Base::TagPlugin\nOverride #call(context)\n→ Array<Tag>"]
268
+ CollectorPlugin["Base::CollectorPlugin\nOverride #collect(ast, buffer)\n→ Array<Hash>"]
269
+ TagValue["Plugin::Tag\nStruct (name, text, types)"]
270
+ Context["Plugin::Context\nMethod snapshot struct"]
271
+ end
272
+
273
+ subgraph Types["External Type System"]
274
+ ProviderChain["ProviderChain\nComposite:\nquery in order\nfirst match wins"]
275
+ RBSProvider["RBS::Provider\n.rbs files\n→ RBS lib"]
276
+ RBSFormatter["RBS::TypeFormatter\nRBS type →\nYARD type string"]
277
+ RBSCollection["RBS::CollectionLoader\nrbs_collection\n.lock.yaml"]
278
+ SorbetBase["Sorbet::BaseProvider\nRBS::Prototype::RBI\nbridge"]
279
+ SorbetSource["Sorbet::SourceProvider\nInline sig{}\ndeclarations"]
280
+ SorbetRBI["Sorbet::RBIProvider\n.rbi files\ndirectories"]
281
+ end
282
+
283
+ Exe --> Run
284
+ Run --> Options
285
+ Run --> InitCmd
286
+ Run --> GenCmd
287
+ Run --> ConfigBuilder
288
+ ConfigBuilder --> ConfigClass
289
+ ConfigClass --> Defaults
290
+ ConfigClass --> Loader
291
+ ConfigClass --> Emit
292
+ ConfigClass --> Filtering
293
+ ConfigClass --> RBSConfig
294
+ ConfigClass --> SorbetConfig
295
+ ConfigClass --> PluginConfig
296
+ Run --> InlineRewriter
297
+ InlineRewriter --> ParsingModule
298
+ ParsingModule --> ParserGem
299
+ ParsingModule --> Prism
300
+ InlineRewriter --> Collector
301
+ Collector --> PluginModule
302
+ PluginModule --> Registry
303
+ Registry --> CollectorPlugin
304
+ InlineRewriter --> DocBuilder
305
+ DocBuilder --> InferModule
306
+ InferModule --> Params
307
+ InferModule --> Returns
308
+ InferModule --> Raises
309
+ Params --> Literals
310
+ Returns --> Literals
311
+ Raises --> ASTWalk
312
+ Raises --> Names
313
+ DocBuilder --> ProviderChain
314
+ ProviderChain --> SorbetSource
315
+ ProviderChain --> SorbetRBI
316
+ ProviderChain --> RBSProvider
317
+ SorbetSource --> SorbetBase
318
+ SorbetRBI --> SorbetBase
319
+ RBSProvider --> RBSFormatter
320
+ RBSProvider --> RBSCollection
321
+ DocBuilder --> PluginModule
322
+ PluginModule --> Registry
323
+ Registry --> TagPlugin
324
+ TagPlugin --> TagValue
325
+ TagPlugin --> Context
326
+ InlineRewriter --> DocBlock
327
+ InlineRewriter --> SourceHelpers
328
+ ```
329
+
330
+ ### Data flow
331
+
332
+ ```mermaid
333
+ flowchart LR
334
+ Input["Source files\n(.rb)"] --> Parse["Parsing.parse_buffer\nParser gem / Prism"]
335
+ Parse --> AST["AST + Comments"]
336
+ AST --> Collect["Collector.process\n· Find methods\n· Track visibility\n· Find attr_*"]
337
+ AST --> CollectPlugins["CollectorPlugin#collect\n· Custom AST walks\n· Non-standard constructs"]
338
+ Collect --> Insertions["Insertion list\n(sorted by position)"]
339
+ CollectPlugins --> Insertions
340
+ Insertions --> Dedup["Deduplicate\n(override by position)"]
341
+ Dedup --> Build["DocBuilder.build_doc_lines\nper insertion"]
342
+ Build --> Infer["Infer params / returns / raises\n(heuristic fallback)"]
343
+ Build --> SigQuery["ProviderChain\nquery external types"]
344
+ Build --> TagPlugins["TagPlugin#call\n(add extra @tags)"]
345
+ Infer --> ResultDoc["Generated YARD doc block"]
346
+ SigQuery --> ResultDoc
347
+ TagPlugins --> ResultDoc
348
+ ResultDoc --> Strategy{"Strategy?"}
349
+ Strategy -->|Safe| Merge["DocBlock.merge\npreserve + append + sort"]
350
+ Strategy -->|Aggressive| Replace["Replace entirely"]
351
+ Merge --> Rewritten["Rewriter#process\n→ rewritten source"]
352
+ Replace --> Rewritten
353
+ Rewritten --> Output["Modified .rb file\n/ STDOUT"]
354
+ ```
355
+
207
356
  ## CLI
208
357
 
209
358
  ```shell
@@ -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
 
@@ -21,53 +22,142 @@ module Docscribe
21
22
  # @param [Hash] 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 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)
32
+ end
33
+
34
+ # Whether any CLI override is present.
35
+ #
36
+ # @note module_function: when included, also defines # (instance visibility: private)
37
+ # @private
38
+ # @param [Hash] options parsed CLI options
39
+ # @return [Boolean]
40
+ def needs_override?(options)
41
+ filter_overrides?(options) ||
42
+ rbs_overrides?(options) ||
43
+ sorbet_overrides?(options)
44
+ end
45
+
46
+ # Whether any method or file filter CLI options were provided.
47
+ #
48
+ # @note module_function: when included, also defines #filter_overrides? (instance visibility: private)
49
+ # @param [Hash] options parsed CLI options
50
+ # @return [Boolean]
51
+ def filter_overrides?(options)
52
+ options[:include].any? ||
26
53
  options[:exclude].any? ||
27
54
  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?
55
+ options[:exclude_file].any?
56
+ end
34
57
 
35
- return base unless needs_override
58
+ # Whether any RBS-related CLI options were provided.
59
+ #
60
+ # @note module_function: when included, also defines #rbs_overrides? (instance visibility: private)
61
+ # @param [Hash] options parsed CLI options
62
+ # @return [Boolean]
63
+ def rbs_overrides?(options)
64
+ options[:rbs] ||
65
+ options[:rbs_collection] ||
66
+ options[:sig_dirs].any?
67
+ end
36
68
 
37
- raw = Marshal.load(Marshal.dump(base.raw))
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
38
90
 
91
+ # Merge CLI method include/exclude patterns into the raw config hash.
92
+ #
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
96
+ # @return [void]
97
+ def apply_method_filters(raw, options)
39
98
  raw['filter'] ||= {}
40
99
  raw['filter']['include'] = Array(raw['filter']['include']) + options[:include]
41
100
  raw['filter']['exclude'] = Array(raw['filter']['exclude']) + options[:exclude]
101
+ end
42
102
 
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
62
- end
103
+ # Merge CLI file include/exclude patterns into the raw config hash.
104
+ #
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
108
+ # @return [void]
109
+ def apply_file_filters(raw, options)
110
+ files = raw['filter']['files'] ||= {} #: Hash[String, untyped]
111
+ files['include'] = Array(files['include']) + options[:include_file]
112
+ files['exclude'] = Array(files['exclude']) + options[:exclude_file]
113
+ end
114
+
115
+ # Apply RBS-related CLI overrides to the raw config.
116
+ #
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
121
+ # @return [void]
122
+ def apply_rbs_overrides(raw, options)
123
+ raw['rbs'] ||= {}
124
+ raw['rbs']['enabled'] = true
125
+ raw['rbs']['sig_dirs'] = Array(raw['rbs']['sig_dirs']) + options[:sig_dirs] if options[:sig_dirs].any?
126
+
127
+ return unless options[:rbs_collection]
63
128
 
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?
129
+ apply_rbs_collection(raw)
130
+ end
131
+
132
+ # Resolve and apply the RBS collection path into the raw config hash.
133
+ #
134
+ # @note module_function: when included, also defines #apply_rbs_collection (instance visibility: private)
135
+ # @param [Hash] raw raw config hash
136
+ # @return [void]
137
+ def apply_rbs_collection(raw)
138
+ require 'docscribe/types/rbs/collection_loader'
139
+ collection_path = Docscribe::Types::RBS::CollectionLoader.resolve
140
+ if collection_path
141
+ raw['rbs']['collection_dirs'] = Array(raw['rbs']['collection_dirs']) + [collection_path]
142
+ else
143
+ warn 'Docscribe: rbs_collection.lock.yaml not found or collection not installed. ' \
144
+ 'Run `bundle exec rbs collection install` first.'
68
145
  end
146
+ end
69
147
 
70
- Docscribe::Config.new(raw)
148
+ # Apply Sorbet-related CLI overrides to the raw config.
149
+ #
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
154
+ # @return [void]
155
+ def apply_sorbet_overrides(raw, options)
156
+ raw['sorbet'] ||= {}
157
+ raw['sorbet']['enabled'] = true
158
+ return unless options[:rbi_dirs].any?
159
+
160
+ raw['sorbet']['rbi_dirs'] = Array(raw['sorbet']['rbi_dirs']) + options[:rbi_dirs]
71
161
  end
72
162
  end
73
163
  end