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
data/README.md CHANGED
@@ -5,6 +5,12 @@
5
5
  [![CI](https://github.com/unurgunite/docscribe/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/unurgunite/docscribe/actions/workflows/ci.yml)
6
6
  [![License](https://img.shields.io/github/license/unurgunite/docscribe.svg)](https://github.com/unurgunite/docscribe/blob/master/LICENSE.txt)
7
7
  [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7-blue.svg)](#installation)
8
+ [![VS Code](https://img.shields.io/badge/VS%20Code-plugin-blue?logo=visualstudiocode)](https://marketplace.visualstudio.com/items?itemName=unurgunite.docscribe-vscode)
9
+ [![RubyMine](https://img.shields.io/badge/RubyMine-plugin-green?logo=jetbrains)](https://plugins.jetbrains.com/plugin/32349-docscribe)
10
+
11
+ <p>
12
+ <img src="assets/icons/icon_256x256.png" alt="Docscribe logo" width="96">
13
+ </p>
8
14
 
9
15
  ![Docscribe before/after demo](docs/image.png)
10
16
 
@@ -29,6 +35,10 @@ returns), and respects Ruby visibility semantics — without using YARD to parse
29
35
  - `attr_reader` / `attr_writer` / `attr_accessor`;
30
36
  - `Struct.new` declarations in both constant-assigned and class-based styles.
31
37
 
38
+ > [!NOTE]
39
+ > Docscribe is under **active development**. If you run into any edge cases or have ideas for improvement, feel free to
40
+ > [open an issue](https://github.com/unurgunite/docscribe/issues/new) or submit a pull request.
41
+
32
42
  Common workflows:
33
43
 
34
44
  - Inspect what safe doc updates would be applied: `docscribe lib`
@@ -38,21 +48,48 @@ Common workflows:
38
48
  - Use RBS signatures when available: `docscribe -a --rbs --sig-dir sig lib`
39
49
  - Use Sorbet signatures when available: `docscribe -a --sorbet --rbi-dir sorbet/rbi lib`
40
50
 
51
+ ## Quick start
52
+
53
+ ```shell
54
+ # Check what safe doc updates would be applied
55
+ docscribe lib
56
+
57
+ # Apply safe updates (insert missing docs, merge existing)
58
+ docscribe -a lib
59
+
60
+ # Rebuild all doc blocks aggressively
61
+ docscribe -A lib
62
+ ```
63
+
64
+ > [!TIP]
65
+ > See [CLI](#cli) for all options and [Update strategies](#update-strategies) for the
66
+ > difference between safe and aggressive modes.
67
+ >
68
+ > Want IDE integration? Check out
69
+ > the [VS Code](https://marketplace.visualstudio.com/items?itemName=unurgunite.docscribe-vscode)
70
+ > and [RubyMine](https://plugins.jetbrains.com/plugin/32349-docscribe) plugins.
71
+
41
72
  ## Contents
42
73
 
43
74
  * [Docscribe](#docscribe)
75
+ * [Quick start](#quick-start)
44
76
  * [Contents](#contents)
45
77
  * [Installation](#installation)
46
- * [Quick start](#quick-start)
47
78
  * [Architecture](#architecture)
48
79
  * [Data flow](#data-flow)
49
80
  * [CLI](#cli)
81
+ * [Exit codes](#exit-codes)
50
82
  * [Options](#options)
51
83
  * [Examples](#examples)
84
+ * [`docscribe sigs` — check RBS signature coverage](#docscribe-sigs--check-rbs-signature-coverage)
85
+ * [`docscribe rbs` — generate RBS from YARD](#docscribe-rbs--generate-rbs-from-yard)
86
+ * [`docscribe update_types` — two-pass type-aware documentation update](#docscribe-update_types--two-pass-type-aware-documentation-update)
87
+ * [`docscribe check_for_comments` — find placeholder documentation](#docscribe-check_for_comments--find-placeholder-documentation)
52
88
  * [Update strategies](#update-strategies)
53
89
  * [Safe strategy](#safe-strategy)
54
90
  * [Aggressive strategy](#aggressive-strategy)
55
91
  * [Output markers](#output-markers)
92
+ * [Tips & tricks](#tips--tricks)
56
93
  * [Parser backend (Parser gem vs Prism)](#parser-backend-parser-gem-vs-prism)
57
94
  * [External type integrations (optional)](#external-type-integrations-optional)
58
95
  * [RBS](#rbs)
@@ -77,6 +114,7 @@ Common workflows:
77
114
  * [Idempotency](#idempotency)
78
115
  * [Plugin examples](#plugin-examples)
79
116
  * [Configuration](#configuration)
117
+ * [Anonymous block parameters](#anonymous-block-parameters)
80
118
  * [Filtering](#filtering)
81
119
  * [`attr_*` example](#attr_-example)
82
120
  * [`Struct.new` examples](#structnew-examples)
@@ -86,14 +124,17 @@ Common workflows:
86
124
  * [Param tag style](#param-tag-style)
87
125
  * [Create a starter config](#create-a-starter-config)
88
126
  * [Generate a plugin skeleton](#generate-a-plugin-skeleton)
127
+ * [Full configuration reference](#full-configuration-reference)
89
128
  * [CI integration](#ci-integration)
90
129
  * [Comparison to YARD's parser](#comparison-to-yards-parser)
91
- * [Mermaid Architecture Reference](#mermaid-architecture-reference)
92
- * [Data flow](#data-flow)
93
130
  * [Limitations](#limitations)
94
131
  * [Roadmap](#roadmap)
132
+ * [Editor Integration](#editor-integration)
133
+ * [VS Code](#vs-code)
134
+ * [RubyMine](#rubymine)
95
135
  * [Contributing](#contributing)
96
136
  * [Discussion & Community](#discussion--community)
137
+ * [Logo Attribution](#logo-attribution)
97
138
  * [License](#license)
98
139
 
99
140
  ## Installation
@@ -118,102 +159,12 @@ gem install docscribe
118
159
 
119
160
  Requires Ruby 2.7+.
120
161
 
121
- ## Quick start
122
-
123
- Given code:
124
-
125
- ```ruby
126
- class Demo
127
- def foo(a, options: {})
128
- 42
129
- end
130
-
131
- def bar(verbose: true)
132
- 123
133
- end
134
-
135
- private
136
-
137
- def self.bump
138
- :ok
139
- end
140
-
141
- class << self
142
- private
143
-
144
- def internal; end
145
- end
146
- end
147
- ```
148
-
149
- Run:
150
-
151
- ```shell
152
- echo "...code above..." | docscribe --stdin
153
- ```
154
-
155
- Output:
156
-
157
- ```ruby
158
- class Demo
159
- # +Demo#foo+ -> Integer
160
- #
161
- # Method documentation.
162
- #
163
- # @param [Object] a Param documentation.
164
- # @param [Hash] options Param documentation.
165
- # @return [Integer]
166
- def foo(a, options: {})
167
- 42
168
- end
169
-
170
- # +Demo#bar+ -> Integer
171
- #
172
- # Method documentation.
173
- #
174
- # @param [Boolean] verbose Param documentation.
175
- # @return [Integer]
176
- def bar(verbose: true)
177
- 123
178
- end
179
-
180
- private
181
-
182
- # +Demo.bump+ -> Symbol
183
- #
184
- # Method documentation.
185
- #
186
- # @return [Symbol]
187
- def self.bump
188
- :ok
189
- end
190
-
191
- class << self
192
- private
193
-
194
- # +Demo.internal+ -> Object
195
- #
196
- # Method documentation.
197
- #
198
- # @private
199
- # @return [Object]
200
- def internal; end
201
- end
202
- end
203
- ```
204
-
205
- > [!NOTE]
206
- > - The tool inserts doc headers before method headers and preserves everything else.
207
- > - For methods with a leading Sorbet `sig`, docs are inserted above the first `sig`.
208
- > - Class methods show with a dot (`+Demo.bump+`, `+Demo.internal+`).
209
- > - Methods inside `class << self` under `private` are marked `@private`.
210
-
211
162
  ## Architecture
212
163
 
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).
164
+ Docscribe is organized into several subsystems. The CLI layer receives user input and orchestrates configuration
165
+ loading, then delegates to the core engine which parses source code, collects methods (using an AST walker), builds YARD
166
+ doc lines — combining heuristic type inference, external RBS/Sorbet signatures, and plugin output — and finally rewrites
167
+ the source via a strategy (safe merge or aggressive replace).
217
168
 
218
169
  ```mermaid
219
170
  flowchart TB
@@ -223,6 +174,9 @@ flowchart TB
223
174
  Options["CLI::Options\nARGV parsing\n(mode, strategy,\nfilters, flags)"]
224
175
  InitCmd["CLI::Init\ndocscribe init\nGenerate config"]
225
176
  GenCmd["CLI::Generate\ndocscribe generate\nScaffold plugins"]
177
+ SigsCmd["CLI::Sigs\ndocscribe sigs\nCheck RBS coverage"]
178
+ RbsGenCmd["CLI::RbsGen\ndocscribe rbs\nGenerate RBS from YARD"]
179
+ SarifFormatter["CLI::Formatters::Sarif\nSARIF 2.1 JSON\nCode Scanning"]
226
180
  ConfigBuilder["CLI::ConfigBuilder\nApply CLI overrides\nto config"]
227
181
  end
228
182
 
@@ -244,10 +198,10 @@ flowchart TB
244
198
  end
245
199
 
246
200
  subgraph Core["Core Engine"]
247
- InlineRewriter["InlineRewriter\n· parse collect\n· deduplicate dispatch\n· rewrite"]
201
+ InlineRewriter["InlineRewriter\n· parse -> collect\n· deduplicate -> dispatch\n· rewrite"]
248
202
  Collector["Collector\n< Parser::AST::Processor\nAST walker\n· find methods/attrs\n· track visibility\n· track containers"]
249
203
  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"]
204
+ DocBlock["DocBlock\nSafe strategy:\nparse -> merge -> sort\nexisting doc blocks"]
251
205
  SourceHelpers["SourceHelpers\nPosition/range\nutilities"]
252
206
  end
253
207
 
@@ -256,34 +210,43 @@ flowchart TB
256
210
  Params["Infer::Params\nParameter type\nfrom name + default"]
257
211
  Returns["Infer::Returns\nReturn type\nfrom method body"]
258
212
  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"]
213
+ Literals["Infer::Literals\nAST literal ->\ntype string"]
214
+ Names["Infer::Names\n:const node ->\nFQN string"]
261
215
  ASTWalk["Infer::ASTWalk\nRecursive DFS\nAST traversal"]
262
216
  end
263
217
 
264
218
  subgraph Plugins["Plugin System"]
265
219
  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>"]
220
+ Registry["Plugin::Registry\nGlobal registry\n· register -> route\n· tag_entries\n· collector_entries"]
221
+ TagPlugin["Base::TagPlugin\nOverride #call(context)\n-> Array<Tag>"]
222
+ CollectorPlugin["Base::CollectorPlugin\nOverride #collect(ast, buffer)\n-> Array<Hash>"]
269
223
  TagValue["Plugin::Tag\nStruct (name, text, types)"]
270
224
  Context["Plugin::Context\nMethod snapshot struct"]
271
225
  end
272
226
 
273
227
  subgraph Types["External Type System"]
274
228
  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"]
229
+ RBSProvider["RBS::Provider\n.rbs files\n-> RBS lib"]
230
+ RBSFormatter["RBS::TypeFormatter\nRBS type ->\nYARD type string"]
277
231
  RBSCollection["RBS::CollectionLoader\nrbs_collection\n.lock.yaml"]
278
232
  SorbetBase["Sorbet::BaseProvider\nRBS::Prototype::RBI\nbridge"]
279
233
  SorbetSource["Sorbet::SourceProvider\nInline sig{}\ndeclarations"]
280
234
  SorbetRBI["Sorbet::RBIProvider\n.rbi files\ndirectories"]
281
235
  end
282
236
 
237
+ subgraph YardTypes["YARD Type Parser"]
238
+ YParser["Yard::Parser\nParse YARD type\nstrings -> AST"]
239
+ YFormatter["Yard::Formatter\nYARD AST ->\nRBS string"]
240
+ YTypes["Yard::Types\n9 AST node types\n(Named, Generic, etc.)"]
241
+ end
242
+
283
243
  Exe --> Run
284
244
  Run --> Options
285
245
  Run --> InitCmd
286
246
  Run --> GenCmd
247
+ Run --> SigsCmd
248
+ Run --> RbsGenCmd
249
+ Run --> SarifFormatter
287
250
  Run --> ConfigBuilder
288
251
  ConfigBuilder --> ConfigClass
289
252
  ConfigClass --> Defaults
@@ -325,6 +288,10 @@ flowchart TB
325
288
  TagPlugin --> Context
326
289
  InlineRewriter --> DocBlock
327
290
  InlineRewriter --> SourceHelpers
291
+ RbsGenCmd --> ParsingModule
292
+ RbsGenCmd --> YParser
293
+ YParser --> YTypes
294
+ YParser --> YFormatter
328
295
  ```
329
296
 
330
297
  ### Data flow
@@ -348,7 +315,7 @@ flowchart LR
348
315
  ResultDoc --> Strategy{"Strategy?"}
349
316
  Strategy -->|Safe| Merge["DocBlock.merge\npreserve + append + sort"]
350
317
  Strategy -->|Aggressive| Replace["Replace entirely"]
351
- Merge --> Rewritten["Rewriter#process\n rewritten source"]
318
+ Merge --> Rewritten["Rewriter#process\n-> rewritten source"]
352
319
  Replace --> Rewritten
353
320
  Rewritten --> Output["Modified .rb file\n/ STDOUT"]
354
321
  ```
@@ -357,16 +324,28 @@ flowchart LR
357
324
 
358
325
  ```shell
359
326
  docscribe [options] [files...]
327
+ docscribe init [options]
328
+ docscribe generate [type] [name] [options]
329
+ docscribe sigs [options] [files...]
330
+ docscribe rbs [options] [files...]
331
+ docscribe update_types [directory]
332
+ docscribe check_for_comments [paths...]
360
333
  ```
361
334
 
362
335
  Docscribe has three main ways to run:
363
336
 
364
- - **Inspect mode** (default): checks what safe doc updates would be applied and exits non-zero if files need changes.
337
+ - **Inspect mode** (default): checks what safe doc updates would be applied and exits 1 if files need changes.
365
338
  - **Safe autocorrect** (`-a`, `--autocorrect`): writes safe, non-destructive updates in place.
366
339
  - **Aggressive autocorrect** (`-A`, `--autocorrect-all`): rewrites existing doc blocks more aggressively.
367
340
  - **STDIN mode** (`--stdin`): reads Ruby source from STDIN and prints rewritten source to STDOUT.
368
341
 
369
- If you pass no files and dont use `--stdin`, Docscribe processes the current directory recursively.
342
+ If you pass no files and don't use `--stdin`, Docscribe processes the current directory recursively.
343
+
344
+ ### Exit codes
345
+
346
+ - **0** — all files are up to date (no changes needed)
347
+ - **1** — some files need documentation updates
348
+ - **2** — execution error (parse error, missing files, etc.)
370
349
 
371
350
  ### Options
372
351
 
@@ -385,10 +364,28 @@ If you pass no files and don’t use `--stdin`, Docscribe processes the current
385
364
  Read source from STDIN and print rewritten output.
386
365
 
387
366
  - `--verbose`
388
- Print per-file actions.
367
+ Print per-file actions. Also enables `--progress`.
368
+
369
+ - `--progress`
370
+ Show progress (`[N/total] path/to/file.rb`) on stderr as each file is processed.
371
+
372
+ - `--quiet` (`-q`)
373
+ Only show status, no details (suppresses change reasons).
374
+ Overrides the default detailed output.
389
375
 
390
376
  - `--explain`
391
- Show detailed reasons for each file that would change.
377
+ Show detailed reasons for each file (default; no-op for compatibility).
378
+
379
+ - `-k`, `--keep-descriptions`
380
+ Preserve existing documentation text when rebuilding doc blocks in aggressive mode.
381
+
382
+ - `-B`, `--no-boilerplate`
383
+ Suppress boilerplate text (`Method documentation.`, `Param documentation.`) in output.
384
+ Equivalent to `emit.include_default_message: false` and `emit.include_param_documentation: false` in config.
385
+
386
+ - `--format FORMAT`
387
+ Output format: `text` (default, human-readable), `json` (machine-readable, RuboCop-compatible), or `sarif` (SARIF 2.1
388
+ JSON, compatible with GitHub Code Scanning).
392
389
 
393
390
  - `--rbs`
394
391
  Use RBS signatures for `@param`/`@return` when available (falls back to inference).
@@ -396,6 +393,12 @@ If you pass no files and don’t use `--stdin`, Docscribe processes the current
396
393
  - `--sig-dir DIR`
397
394
  Add an RBS signature directory (repeatable). Implies `--rbs`.
398
395
 
396
+ - `--sorbet`
397
+ Use Sorbet signatures for `@param`/`@return` when available (falls back to inference).
398
+
399
+ - `--rbi-dir DIR`
400
+ Add an Sorbet RBI directory (repeatable). Implies `--sorbet`.
401
+
399
402
  - `--include PATTERN`
400
403
  Include PATTERN (method id or file path; glob or `/regex/`).
401
404
 
@@ -456,9 +459,146 @@ If you pass no files and don’t use `--stdin`, Docscribe processes the current
456
459
 
457
460
  - Show detailed reasons for files that would change:
458
461
  ```shell
459
- docscribe --verbose --explain lib
462
+ docscribe --verbose lib
460
463
  ```
461
464
 
465
+ ### `docscribe sigs` — check RBS signature coverage
466
+
467
+ > [!WARNING]
468
+ > `docscribe sigs` requires **Ruby 3.0+** and the `rbs` gem. On Ruby 2.7 it will print an error and exit with code 2.
469
+
470
+ `docscribe sigs` parses Ruby files, extracts method definitions, and checks each
471
+ method against the configured RBS signature directories. Reports methods that lack RBS type signatures.
472
+
473
+ ```shell
474
+ # Check lib/ against sig/ signatures
475
+ docscribe sigs lib
476
+
477
+ # Use RBS collection
478
+ docscribe sigs --rbs-collection lib
479
+
480
+ # Multiple signature directories
481
+ docscribe sigs -s sig -s vendor/sigs lib
482
+
483
+ # Verbose output (also prints methods that have signatures)
484
+ docscribe sigs --verbose lib
485
+ ```
486
+
487
+ **Flags:**
488
+
489
+ - `-s`, `--sig-dir DIR` — add an RBS signature directory (repeatable). Default: `sig`.
490
+ - `--rbs-collection` — use RBS collection (reads `rbs_collection.lock.yaml`).
491
+ - `--verbose` — also print methods that have signatures.
492
+ - `-h`, `--help` — show help.
493
+
494
+ **Exit codes:**
495
+
496
+ - `0` — all methods have RBS signatures.
497
+ - `1` — some methods lack RBS signatures.
498
+ - `2` — error occurred.
499
+
500
+ ### `docscribe rbs` — generate RBS from YARD
501
+
502
+ > [!WARNING]
503
+ > `docscribe rbs` requires **Ruby 3.0+** and the `rbs` gem. On Ruby 2.7 it will print an error and exit with code 2.
504
+
505
+ `docscribe rbs` parses YARD comments (`@param`, `@return`, `@option`) from Ruby files
506
+ and generates corresponding `.rbs` signature files.
507
+
508
+ ```shell
509
+ # Generate RBS files in sig/
510
+ docscribe rbs lib
511
+
512
+ # Print to stdout (no files written)
513
+ docscribe rbs -n lib
514
+
515
+ # Specify output directory
516
+ docscribe rbs -o sig/gen lib
517
+
518
+ # Overwrite existing files
519
+ docscribe rbs -f lib
520
+ ```
521
+
522
+ **Flags:**
523
+
524
+ - `-o`, `--output DIR` — output directory (default: `sig`).
525
+ - `-n`, `--dry-run` — print generated RBS to stdout, do not write files.
526
+ - `-f`, `--force` — overwrite existing files.
527
+ - `-h`, `--help` — show help.
528
+
529
+ **Exit codes:**
530
+
531
+ - `0` — success.
532
+ - `1` — errors occurred during processing.
533
+ - `2` — execution error (no files found).
534
+
535
+ ```ruby
536
+ # Example: lib/user.rb
537
+ class User
538
+ # @param [String] name
539
+ # @param [Integer] age
540
+ # @return [User]
541
+ def initialize(name, age)
542
+ @name = name
543
+ @age = age
544
+ end
545
+ end
546
+ ```
547
+
548
+ ```shell
549
+ docscribe rbs lib
550
+ # Generated sig/user.rbs
551
+ ```
552
+
553
+ ```ruby.rbs
554
+ # sig/user.rbs
555
+ class User
556
+ def initialize: (String name, Integer age) -> User
557
+ end
558
+ ```
559
+
560
+ ### `docscribe update_types` — two-pass type-aware documentation update
561
+
562
+ > [!NOTE]
563
+ > `docscribe update_types` is a convenience alias for the two-pass workflow above. It requires Ruby 3.0+ and the `rbs`
564
+ > gem (because of `--rbs-collection`). The RBS collection must be set up first with
565
+ > `bundle exec rbs collection install`. Type accuracy depends on your RBS signatures — if signatures are incomplete or
566
+ > missing, types will fall back to AST inference.
567
+
568
+ `docscribe update_types` runs two passes to bring both docs and RBS signatures up to date:
569
+
570
+ 1. **Pass 1** — `docscribe -AkB --rbs-collection <dir>`: aggressively rebuilds doc blocks, preserves existing
571
+ descriptions, suppresses boilerplate, uses RBS collection types.
572
+ 2. **Pass 2** — `docscribe -aB --rbs-collection <dir>`: safe merge cleanup with no boilerplate.
573
+
574
+ ```shell
575
+ # Update docs in lib/ using RBS collection
576
+ docscribe update_types lib
577
+
578
+ # Defaults to current directory
579
+ docscribe update_types
580
+ ```
581
+
582
+ ### `docscribe check_for_comments` — find placeholder documentation
583
+
584
+ `docscribe check_for_comments` scans Ruby source files for YARD comments that still contain default placeholder
585
+ text. Reads the configured placeholder messages from `docscribe.yml` (or built-in defaults). Useful in CI to catch
586
+ files where auto-generated text was never replaced with real documentation.
587
+
588
+ ```shell
589
+ # Scan entire project
590
+ docscribe check_for_comments
591
+
592
+ # Scan specific directories
593
+ docscribe check_for_comments lib app
594
+ ```
595
+
596
+ Exit code `0` if no placeholders found, `1` if any are detected.
597
+
598
+ **Flags:**
599
+
600
+ - `-h`, `--help` — show help.
601
+
462
602
  ## Update strategies
463
603
 
464
604
  Docscribe supports two update strategies: **safe** and **aggressive**.
@@ -493,6 +633,9 @@ Aggressive strategy:
493
633
 
494
634
  Use it when you want to rebaseline or regenerate docs wholesale.
495
635
 
636
+ > [!CAUTION]
637
+ > Aggressive mode rewrites existing doc blocks entirely. Review changes with `git diff` before committing.
638
+
496
639
  ### Output markers
497
640
 
498
641
  In inspect mode, Docscribe prints one character per file:
@@ -500,14 +643,20 @@ In inspect mode, Docscribe prints one character per file:
500
643
  - `.` = file is up to date
501
644
  - `F` = file would change
502
645
  - `E` = file had an error
646
+ - `M` = type mismatch detected (external RBS/Sorbet signature differs from inferred type)
503
647
 
504
648
  In write modes:
505
649
 
506
650
  - `.` = file already OK
507
651
  - `C` = file was updated
508
652
  - `E` = file had an error
653
+ - `M` = file updated but type mismatch remains
509
654
 
510
- With `--verbose`, Docscribe prints per-file statuses instead.
655
+ > [!IMPORTANT]
656
+ > The `M` marker only appears when `--rbs` or `--sorbet` is enabled, since type mismatches are detected by
657
+ > comparing external signatures against inferred types.
658
+
659
+ With `--verbose`, Docscribe prints per-file statuses instead; type mismatches show as `MT` with the specific difference.
511
660
 
512
661
  With `--explain`, Docscribe also prints detailed reasons, such as:
513
662
 
@@ -516,6 +665,27 @@ With `--explain`, Docscribe also prints detailed reasons, such as:
516
665
  - missing module_function note
517
666
  - unsorted tags
518
667
 
668
+ Use `--quiet` to suppress these details and show only file names and the summary line.
669
+
670
+ ## Tips & tricks
671
+
672
+ Useful flag combinations for common workflows:
673
+
674
+ - `docscribe -aB lib` — safe autocorrect without boilerplate. Uses safe mode but suppresses default text, leaving only
675
+ type tags (`@param`, `@return`). Clean minimal docs.
676
+ - `docscribe -AkB lib` — aggressive autocorrect with preserved descriptions and no boilerplate. Rebuilds doc blocks,
677
+ keeps your hand-written descriptions, suppresses default text. Best for rebaselining.
678
+ - `docscribe -a --rbs-collection lib` — safe autocorrect using RBS gem collection signatures. Recommended for Rails
679
+ projects.
680
+ - `docscribe -a --sorbet --rbi-dir sorbet/rbi lib` — safe autocorrect using Sorbet RBI signatures.
681
+ - `docscribe update_types lib` — two-pass type-aware update: aggressively rebuilds docs with kept descriptions and RBS
682
+ collection, then safe-merges to clean up.
683
+ See [docscribe update_types](#docscribe-update_types--two-pass-type-aware-documentation-update).
684
+
685
+ > [!NOTE]
686
+ > `docscribe update_types` is a convenient shortcut, but be aware it uses `--rbs-collection` under the hood.
687
+ > If your RBS signatures are incomplete, types may fall back to AST inference.
688
+
519
689
  ## Parser backend (Parser gem vs Prism)
520
690
 
521
691
  Docscribe internally works with `parser`-gem-compatible AST nodes and `Parser::Source::*` objects (so it can use
@@ -524,6 +694,11 @@ Docscribe internally works with `parser`-gem-compatible AST nodes and `Parser::S
524
694
  - On Ruby **<= 3.3**, Docscribe parses using the `parser` gem.
525
695
  - On Ruby **>= 3.4**, Docscribe parses using **Prism** and translates the tree into the `parser` gem's AST.
526
696
 
697
+ > [!NOTE]
698
+ > Prism support is automatic on Ruby 3.4+. On earlier Rubies, the `parser` gem is always used.
699
+ > You can override with `DOCSCRIBE_PARSER_BACKEND=prism` on Ruby 3.1+ if you have the `prism` gem installed,
700
+ > but this is not recommended for production use.
701
+
527
702
  You can force a backend with an environment variable:
528
703
 
529
704
  ```shell
@@ -537,16 +712,33 @@ Docscribe can improve generated `@param` and `@return` types by reading external
537
712
  AST inference.
538
713
 
539
714
  > [!IMPORTANT]
540
- > When external type information is available, Docscribe resolves signatures in this order:
541
- > - inline Sorbet `sig` declarations in the current Ruby source;
542
- > - Sorbet RBI files;
543
- > - RBS files;
544
- > - AST inference fallback.
715
+ > Docscribe resolves types in a two-level chain. For **documentation tags** (`@param`, `@return`), the priority is:
716
+ >
717
+ > | Priority | Source | When active |
718
+ > |----------|-------------------------------------------------------------|----------------------------------------------------------|
719
+ > | **1** | Inline Sorbet `sig { ... }` in current file | `--sorbet` or `sorbet.enabled: true` |
720
+ > | **2** | Sorbet RBI files (`.rbi`) | `--sorbet --rbi-dir` or `sorbet.rbi_dirs` |
721
+ > | **3** | RBS files (sig_dirs + collection, loaded into one env) | `--rbs --sig-dir` / `--rbs-collection` or `rbs.*` config |
722
+ > | **3a** | └─ Fallback: sig_dirs only (collection dropped on conflict) | Automatic if priority 3 env load fails |
723
+ > | **4** | AST inference (fallback) | Always active |
724
+ >
725
+ > For **intra-method body inference** (e.g. resolving `Integer#positive?` -> `Boolean`), a separate core RBS provider
726
+ > loads only stdlib/built-in signatures and is active automatically when the `rbs` gem is available — even without
727
+ > `--rbs`.
545
728
  >
546
- > If an external signature cannot be loaded or parsed, Docscribe falls back to normal inference instead of failing.
729
+ > If an external signature cannot be loaded or parsed, Docscribe falls back to the next source in the chain instead of
730
+ > failing.
547
731
 
548
732
  ### RBS
549
733
 
734
+ > [!IMPORTANT]
735
+ > All RBS features require the `rbs` gem. Add `gem "rbs"` to your Gemfile and run `bundle install`,
736
+ > or install it globally with `gem install rbs`.
737
+ >
738
+ > On Ruby 2.7 the `rbs` gem cannot load at all — `--rbs`, `--sig-dir`, `--rbs-collection`,
739
+ > `docscribe sigs`, and `docscribe rbs` will print a warning and fall back to AST-only inference.
740
+ > On Ruby 3.0+, if the gem is missing, Docscribe silently falls back to inference when you pass RBS flags.
741
+
550
742
  Docscribe can read method signatures from `.rbs` files and use them to generate more accurate parameter and return
551
743
  types.
552
744
 
@@ -566,12 +758,23 @@ Config:
566
758
 
567
759
  ```yaml
568
760
  rbs:
569
- enabled: true
761
+ enabled: false
570
762
  sig_dirs:
571
763
  - sig
764
+ collection: false
572
765
  collapse_generics: false
766
+ collapse_object_generics: false
767
+ warn_missing_collection: true
573
768
  ```
574
769
 
770
+ - `collection` — enable auto-discovery of RBS collection from `rbs_collection.lock.yaml`. Pass `--rbs-collection` on the
771
+ CLI.
772
+ - `collapse_generics` — strip all generic type arguments (e.g. `Array<String>` -> `Array`).
773
+ - `collapse_object_generics` — only strip generic arguments when all are `Object` (e.g. `Hash<Object, Object>` ->
774
+ `Hash`, but `Hash<Symbol, Object>` stays).
775
+ - `warn_missing_collection` — warn on stderr when `rbs_collection.lock.yaml` is found but collection is not enabled. Set
776
+ to `false` to suppress.
777
+
575
778
  Example:
576
779
 
577
780
  ```ruby
@@ -794,24 +997,29 @@ end
794
997
 
795
998
  ### Generic type formatting
796
999
 
797
- Both RBS and Sorbet integrations support `collapse_generics`.
1000
+ Both RBS and Sorbet integrations support generic type collapsing.
1001
+
1002
+ **`collapse_generics`** — strips all generic type arguments.
1003
+ **`collapse_object_generics`** — only strips arguments when all are `Object` (e.g. `Hash<Object, Object>` -> `Hash`, but
1004
+ `Hash<Symbol, Object>` stays).
798
1005
 
799
- When disabled:
1006
+ When both disabled:
800
1007
 
801
1008
  ```yaml
802
1009
  rbs:
803
1010
  collapse_generics: false
1011
+ collapse_object_generics: false
804
1012
 
805
1013
  sorbet:
806
1014
  collapse_generics: false
807
1015
  ```
808
1016
 
809
- Docscribe preserves generic container details where possible, for example:
1017
+ Docscribe preserves generic container details, for example:
810
1018
 
811
1019
  - `Array<String>`
812
1020
  - `Hash<Symbol, Integer>`
813
1021
 
814
- When enabled:
1022
+ When `collapse_generics` is enabled:
815
1023
 
816
1024
  ```yaml
817
1025
  rbs:
@@ -821,7 +1029,7 @@ sorbet:
821
1029
  collapse_generics: true
822
1030
  ```
823
1031
 
824
- Docscribe simplifies container types to their outer names, for example:
1032
+ Docscribe simplifies all container types to their outer names, for example:
825
1033
 
826
1034
  - `Array`
827
1035
  - `Hash`
@@ -856,6 +1064,11 @@ Return values:
856
1064
  - For simple bodies, Docscribe looks at the last expression or explicit `return`.
857
1065
  - Unions with `nil` become optional types (e.g. `String` or `nil` -> `String?`).
858
1066
  - For control flow (`if`/`case`), it unifies branches conservatively.
1067
+
1068
+ > [!TIP]
1069
+ > Docscribe resolves return types for core Ruby methods (`Integer#positive?`, `String#upcase`, etc.)
1070
+ > **even without `--rbs`** — the core RBS provider is always active when the `rbs` gem is available.
1071
+
859
1072
  - **RBS core type inference**: when `--rbs` is enabled, Docscribe resolves return types for method calls on core types
860
1073
  from their RBS definitions:
861
1074
  - `arg.positive?` (`arg = 1`) -> `Boolean` (from `Integer#positive?`)
@@ -1168,6 +1381,19 @@ Sample plugins available at [examples](examples/plugins):
1168
1381
 
1169
1382
  Docscribe can be configured via a YAML file (`docscribe.yml` by default, or pass `--config PATH`).
1170
1383
 
1384
+ ### Anonymous block parameters
1385
+
1386
+ Ruby 3.2+ allows anonymous block arguments (`def foo(&)`). By default, Docscribe generates `@param [Proc] block` for
1387
+ these — but since the parameter has no name, the tag is misleading.
1388
+
1389
+ To suppress the `@param` tag for anonymous block arguments:
1390
+
1391
+ ```yaml
1392
+ skip_anonymous_block_params: true
1393
+ ```
1394
+
1395
+ When `false` (default), anonymous block params generate `@param [Proc] block`.
1396
+
1171
1397
  ### Filtering
1172
1398
 
1173
1399
  Docscribe can filter both *files* and *methods*.
@@ -1391,6 +1617,7 @@ docscribe generate tag SincePlugin --stdout
1391
1617
  ```
1392
1618
 
1393
1619
  The generated file contains:
1620
+
1394
1621
  - the correct base class (`Base::TagPlugin` or `Base::CollectorPlugin`)
1395
1622
  - inline comments describing every available `Context` attribute (TagPlugin)
1396
1623
  or the expected return shape (CollectorPlugin)
@@ -1401,6 +1628,58 @@ The generated file contains:
1401
1628
  > The class name must be a valid Ruby constant (`MyPlugin`, `My::Plugin`).
1402
1629
  > The output filename is the snake_case equivalent (`my_plugin.rb`, `my/plugin.rb`).
1403
1630
 
1631
+ ### Full configuration reference
1632
+
1633
+ <details>
1634
+ <summary>Click to expand — 43 config keys</summary>
1635
+
1636
+ | Key | Type | Default | Description |
1637
+ |-------------------------------------------|------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|
1638
+ | `keep_descriptions` | `bool` | `false` | Preserve existing doc text in aggressive mode |
1639
+ | `skip_anonymous_block_params` | `bool` | `false` | Skip `@param [Proc] block` for anonymous `&` params |
1640
+ | `emit.header` | `bool` | `false` | Generate method header line (`+#foo+ -> ...`) |
1641
+ | `emit.include_default_message` | `bool` | `true` | Insert default message (`Method documentation.`) |
1642
+ | `emit.include_param_documentation` | `bool` | `true` | Insert param description text (`Param documentation.`) |
1643
+ | `emit.param_tags` | `bool` | `true` | Generate `@param` tags |
1644
+ | `emit.return_tag` | `bool` | `true` | Generate `@return` tag |
1645
+ | `emit.visibility_tags` | `bool` | `true` | Generate `@private`/`@protected` tags |
1646
+ | `emit.raise_tags` | `bool` | `true` | Generate `@raise` tags (for `raise` in method body) |
1647
+ | `emit.rescue_conditional_returns` | `bool` | `true` | Consider `rescue` branches in return type inference |
1648
+ | `emit.attributes` | `bool` | `false` | Generate `@!attribute` for `attr_*` and `Struct.new` |
1649
+ | `doc.default_message` | `string` | `"Method documentation."` | Default text for method description |
1650
+ | `doc.param_documentation` | `string` | `"Param documentation."` | Default text for param description |
1651
+ | `doc.sort_tags` | `bool` | `true` | Sort tags according to `tag_order` |
1652
+ | `doc.tag_order` | `string[]` | `["todo","note","api","private","protected","param","option","yieldparam","raise","return"]` | Tag sort order |
1653
+ | `doc.param_tag_style` | `string` | `"type_name"` | `@param` style: `type_name` (`[Type] name`) or `name_type` (`name [Type]`) |
1654
+ | `inference.fallback_type` | `string` | `"Object"` | Fallback type when inference yields no result |
1655
+ | `inference.nil_as_optional` | `bool` | `true` | Treat `nil` as an optional type |
1656
+ | `inference.treat_options_keyword_as_hash` | `bool` | `true` | Treat `**options` as `Hash` in `@option` tags |
1657
+ | `filter.include` | `string[]` | `[]` | Only include methods matching pattern |
1658
+ | `filter.exclude` | `string[]` | `[]` | Exclude methods matching pattern |
1659
+ | `filter.visibilities` | `string[]` | `["public","protected","private"]` | Visibilities to process |
1660
+ | `filter.scopes` | `string[]` | `["instance","class"]` | Scopes to process |
1661
+ | `filter.files.include` | `string[]` | `[]` | Only process files matching pattern |
1662
+ | `filter.files.exclude` | `string[]` | `["spec"]` | Skip files matching pattern |
1663
+ | `methods.instance.public` | `object` | `{}` | Overrides for instance public methods |
1664
+ | `methods.instance.protected` | `object` | `{}` | Overrides for instance protected methods |
1665
+ | `methods.instance.private` | `object` | `{}` | Overrides for instance private methods |
1666
+ | `methods.class.public` | `object` | `{}` | Overrides for class public methods |
1667
+ | `methods.class.protected` | `object` | `{}` | Overrides for class protected methods |
1668
+ | `methods.class.private` | `object` | `{}` | Overrides for class private methods |
1669
+ | `rbs.enabled` | `bool` | `false` | Enable RBS integration |
1670
+ | `rbs.collection` | `bool` | `false` | Auto-discover RBS collection from `rbs_collection.lock.yaml` |
1671
+ | `rbs.sig_dirs` | `string[]` | `["sig"]` | RBS signature directories |
1672
+ | `rbs.collection_dirs` | `string[]` | `[]` | RBS collection directories (set automatically) |
1673
+ | `rbs.collapse_generics` | `bool` | `false` | Strip generic arguments (`Array<String>` -> `Array`) |
1674
+ | `rbs.collapse_object_generics` | `bool` | `false` | Strip generics only when all are `Object` |
1675
+ | `rbs.warn_missing_collection` | `bool` | `true` | Warn when `rbs_collection.lock.yaml` found but collection not enabled |
1676
+ | `sorbet.enabled` | `bool` | `false` | Enable Sorbet integration |
1677
+ | `sorbet.rbi_dirs` | `string[]` | `["sorbet/rbi","rbi"]` | RBI file directories |
1678
+ | `sorbet.collapse_generics` | `bool` | `false` | Strip generic arguments from Sorbet signatures |
1679
+ | `plugins.require` | `string[]` | `[]` | Plugin paths for `require` |
1680
+
1681
+ </details>
1682
+
1404
1683
  ## CI integration
1405
1684
 
1406
1685
  Fail the build if files would need safe updates:
@@ -1417,11 +1696,33 @@ Apply safe fixes before the test stage:
1417
1696
  run: docscribe -a lib
1418
1697
  ```
1419
1698
 
1420
- Aggressively rebuild docs:
1699
+ Rebuild docs aggressively, preserve descriptions, suppress boilerplate, with verbose output:
1421
1700
 
1422
1701
  ```yaml
1423
1702
  - name: Rebuild inline docs
1424
- run: docscribe -A lib
1703
+ run: docscribe -AkB --verbose
1704
+ ```
1705
+
1706
+ Run static type checking with Steep (requires Ruby ≥ 3.2):
1707
+
1708
+ ```yaml
1709
+ - name: Steep (type check)
1710
+ if: ${{ matrix.ruby >= 3.2 }}
1711
+ run: bundle exec steep check
1712
+ ```
1713
+
1714
+ Fail the build if any generated docs still contain default placeholder text:
1715
+
1716
+ ```yaml
1717
+ - name: Check for placeholder docs
1718
+ run: docscribe check_for_comments lib/
1719
+ ```
1720
+
1721
+ For stricter validation, check for empty doc blocks:
1722
+
1723
+ ```yaml
1724
+ - name: Check for empty doc blocks
1725
+ run: "! grep -rn '^ # $' lib/"
1425
1726
  ```
1426
1727
 
1427
1728
  ## Comparison to YARD's parser
@@ -1450,13 +1751,39 @@ yard doc -o docs
1450
1751
  ## Roadmap
1451
1752
 
1452
1753
  - Method behavior inference from AST;
1453
- - YAML-based plugin configuration;
1754
+ - Deeper YARD integration — parse `.c` source comments and generate docs for C-extensions;
1755
+ - Custom parser plugins — support non-Ruby languages (Crystal, etc.) via the plugin system;
1454
1756
  - Effective config dump;
1455
- - JSON output;
1456
1757
  - Overload-aware signature selection;
1457
1758
  - Manual `@!attribute` merge policy;
1458
1759
  - Richer inference for common APIs;
1459
- - Editor integration.
1760
+ - Editor integration (LSP, VS Code extension);
1761
+ - Documentation coverage report — percentage of documented methods, params, returns;
1762
+ - Pre-commit hook auto-install (`docscribe init --pre-commit`);
1763
+ - Parallel processing for large codebases.
1764
+
1765
+ ## Editor Integration
1766
+
1767
+ Docscribe provides IDE plugins for a better development experience:
1768
+
1769
+ ### VS Code
1770
+
1771
+ [![VS Code](https://img.shields.io/badge/VS%20Code-plugin-blue?logo=visualstudiocode)](https://marketplace.visualstudio.com/items?itemName=unurgunite.docscribe-vscode)
1772
+
1773
+ - **Repository:** [github.com/FlorexLabs/docscribe-vscode](https://github.com/FlorexLabs/docscribe-vscode)
1774
+ - **Marketplace:** [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=unurgunite.docscribe-vscode)
1775
+ - **Features:** inline diagnostics, quick-fix, auto-check on save, status bar
1776
+
1777
+ ### RubyMine
1778
+
1779
+ [![RubyMine](https://img.shields.io/badge/RubyMine-plugin-green?logo=jetbrains)](https://plugins.jetbrains.com/plugin/32349-docscribe)
1780
+
1781
+ - **Repository:** [github.com/FlorexLabs/docscribe-rubymine](https://github.com/FlorexLabs/docscribe-rubymine)
1782
+ - **Marketplace:** [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/32349-docscribe)
1783
+ - **Features:** ExternalAnnotator, AnAction, IntentionAction, Settings UI
1784
+
1785
+ > [!NOTE]
1786
+ > Both plugins require **docscribe >= 1.5.0**.
1460
1787
 
1461
1788
  ## Contributing
1462
1789
 
@@ -1471,6 +1798,14 @@ bundle exec rubocop
1471
1798
  - [Dev.to article](https://dev.to/unurgunite)
1472
1799
  - [GitHub Discussions](https://github.com/unurgunite/docscribe/discussions)
1473
1800
 
1801
+ ## Logo Attribution
1802
+
1803
+ The Docscribe logo uses the Ruby gem icon by [FlorexLabs](https://github.com/FlorexLabs).
1804
+
1805
+ <p>
1806
+ <img src="assets/icons/icon_128x128.png" alt="Docscribe logo">
1807
+ </p>
1808
+
1474
1809
  ## License
1475
1810
 
1476
1811
  MIT