yard-lint 1.5.2 → 1.6.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/CHANGELOG.md +18 -2
  3. data/README.md +165 -7
  4. data/bin/yard-lint +45 -11
  5. data/lib/yard/lint/executor/in_process_registry.rb +38 -12
  6. data/lib/yard/lint/results/base.rb +3 -0
  7. data/lib/yard/lint/runner.rb +4 -2
  8. data/lib/yard/lint/templates/default_config.yml +40 -0
  9. data/lib/yard/lint/templates/strict_config.yml +39 -0
  10. data/lib/yard/lint/validators/base.rb +107 -1
  11. data/lib/yard/lint/validators/documentation/line_length/config.rb +21 -0
  12. data/lib/yard/lint/validators/documentation/line_length/messages_builder.rb +26 -0
  13. data/lib/yard/lint/validators/documentation/line_length/parser.rb +65 -0
  14. data/lib/yard/lint/validators/documentation/line_length/result.rb +26 -0
  15. data/lib/yard/lint/validators/documentation/line_length/validator.rb +59 -0
  16. data/lib/yard/lint/validators/documentation/line_length.rb +43 -0
  17. data/lib/yard/lint/validators/documentation/missing_return/config.rb +2 -1
  18. data/lib/yard/lint/validators/documentation/missing_return/parser.rb +0 -1
  19. data/lib/yard/lint/validators/documentation/missing_return/validator.rb +1 -0
  20. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/config.rb +20 -0
  21. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/messages_builder.rb +23 -0
  22. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/parser.rb +38 -0
  23. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/result.rb +24 -0
  24. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/validator.rb +121 -0
  25. data/lib/yard/lint/validators/documentation/orphaned_doc_comment.rb +53 -0
  26. data/lib/yard/lint/validators/documentation/text_substitution/config.rb +24 -0
  27. data/lib/yard/lint/validators/documentation/text_substitution/messages_builder.rb +33 -0
  28. data/lib/yard/lint/validators/documentation/text_substitution/parser.rb +57 -0
  29. data/lib/yard/lint/validators/documentation/text_substitution/result.rb +24 -0
  30. data/lib/yard/lint/validators/documentation/text_substitution/validator.rb +72 -0
  31. data/lib/yard/lint/validators/documentation/text_substitution.rb +55 -0
  32. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/config.rb +2 -1
  33. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +1 -0
  34. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/config.rb +3 -1
  35. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +3 -1
  36. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +1 -2
  37. data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
  38. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +1 -0
  39. data/lib/yard/lint/validators/documentation/undocumented_options/config.rb +2 -1
  40. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +1 -0
  41. data/lib/yard/lint/validators/documentation/undocumented_options.rb +1 -2
  42. data/lib/yard/lint/validators/tags/api_tags/result.rb +1 -0
  43. data/lib/yard/lint/validators/tags/api_tags/validator.rb +1 -1
  44. data/lib/yard/lint/validators/tags/example_style/result.rb +1 -0
  45. data/lib/yard/lint/validators/tags/example_syntax/result.rb +1 -0
  46. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +1 -1
  47. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +1 -1
  48. data/lib/yard/lint/validators/tags/missing_yield/config.rb +20 -0
  49. data/lib/yard/lint/validators/tags/missing_yield/messages_builder.rb +22 -0
  50. data/lib/yard/lint/validators/tags/missing_yield/parser.rb +39 -0
  51. data/lib/yard/lint/validators/tags/missing_yield/result.rb +24 -0
  52. data/lib/yard/lint/validators/tags/missing_yield/validator.rb +76 -0
  53. data/lib/yard/lint/validators/tags/missing_yield.rb +56 -0
  54. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +4 -2
  55. data/lib/yard/lint/validators/tags/redundant_param_description.rb +2 -1
  56. data/lib/yard/lint/validators/tags/type_syntax.rb +1 -2
  57. data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +1 -2
  58. data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +1 -2
  59. data/lib/yard/lint/version.rb +1 -1
  60. data/lib/yard/lint.rb +14 -4
  61. metadata +25 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c96b4e1a2adb1c849b9716bbf413db8a9515cab506a350219351f56f140e19c
4
- data.tar.gz: c5857861bf183876d1030d9b07b7ed295cb08f6951629d651a54a2aec4e4df54
3
+ metadata.gz: ef1425a1be17cac04595b6558a742eb4036fc1cc66891e1d8f31eb46094c8914
4
+ data.tar.gz: d5ba0ef812eb0261718d7259d2da0b0f0b2fa37a1674893f6d7d3178be3bf551
5
5
  SHA512:
6
- metadata.gz: 1c3b748cfcc894cd34715e4cda65a62b321fb1d96372a3b49934c2516dc8f74e60ab1cf3e632cca6491da37951a0b0713149f57545aae6d495b6e45530bc169b
7
- data.tar.gz: 95132fedf072bb07466bc1d83e8a3b0bcc37df8e5ad85a811e20ddeaf8c30c354af89282de66df03cbcfc2fdae7721637772863828eba3a2b228e95993985faa
6
+ metadata.gz: 23d60c91521b5afbadd0a43380682686f7b10aa68a968f5091da2de425f477f71ce3b8a6477ca5d0197d7138979dffc4a0aebe2582b3a64cd4092b18b6222c95
7
+ data.tar.gz: 966fe65d5e72ba1dbf225fe8802f2259dcdb436a66b2571d6caf7aff7d59491e1b337c7fe4db0e68354bceebb192c89d40ed6c1229b5e2bd2f75d2f5cbde456a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # YARD-Lint Changelog
2
2
 
3
+ ## 1.6.0 (2026-06-11)
4
+ - **[Feature]** New `--format quickfix` output mode emits one offense per line in the standard `file:line: S: Validator: message` format. Vim users can set `makeprg=yard-lint\ --format\ quickfix\ --no-progress\ %` and navigate offenses with `:cnext`/`:cprev`; Emacs users can use it as their `compile-command` and navigate with `M-g n`/`M-g p`. Produces no output (and exits 0) when there are no offenses.
5
+ - **[Feature]** New opt-in validator `Documentation/LineLength` detects documentation comment lines that exceed a configurable maximum length (#176). Disabled by default (`Enabled: false`) to avoid breaking existing projects; enable with `Documentation/LineLength: Enabled: true` and tune with `MaxLength: 120` (default). Uses YARD's already-parsed docstring to determine which source lines belong to the comment block, avoiding fragile backwards-scanning. Each over-length line produces a separate offense at its exact file location.
6
+ - **[Feature]** `Yard::Lint.run` now accepts an optional `source:` keyword argument for linting in-memory source without reading from disk. `path:` is still required and governs config resolution, exclusion matching, and offense location reporting — only the source bytes come from the caller. The CLI gains a corresponding `--stdin` flag (`cat lib/foo.rb | yard-lint --stdin lib/foo.rb`). Enables LSP/editor integration tools (e.g. `solargraph-yard-lint`) to lint unsaved buffers without waiting for a save. (#173)
7
+ - **[Feature]** `Documentation/UndocumentedMethodArguments` now supports an `AllowedMethods` config option. Methods listed there are silently skipped for `@param` documentation checks. Three pattern forms are supported: exact name (`call`), arity notation (`initialize/1` — matches only that parameter count, with `*` and `&` params excluded from the count), and regex (`/^perform/`). Invalid regex patterns are silently ignored; the empty regex `//` is always rejected. Useful for idiomatic Ruby conventions like service objects where `call(args)` is self-documenting, or `respond_to_missing?` / `method_missing` whose arguments are rarely worth documenting.
8
+ - **[Feature]** All Documentation validators (`Documentation/UndocumentedObjects`, `Documentation/UndocumentedMethodArguments`, `Documentation/UndocumentedBooleanMethods`, `Documentation/UndocumentedOptions`, `Documentation/MissingReturn`) now support an `AllowedParentClasses` config option. When set, any class or method whose enclosing class directly inherits from one of the listed base classes is silently skipped by that validator. Useful for exempting exception hierarchies (`StandardError`), ORM model methods (`ActiveRecord::Base`, `ApplicationRecord`), or any framework base class whose subclasses don't need YARD coverage. Uses exact full-path matching (`"ActiveRecord::Base"`, not `"Base"`). `Object` and `BasicObject` are never matched to avoid accidentally exempting all classes.
9
+ - **[Feature]** New opt-in validator `Tags/MissingYield` detects methods that call `yield` in their body but do not document the block with a `@yield`, `@yieldparam`, or `@yieldreturn` tag. Callers need to know a method yields in order to pass a block; the validator checks raw docstring text rather than YARD's inferred tag list, so YARD's automatic `@yield` inference for bare `yield expr` statements does not suppress the offense. Method calls like `Fiber.yield` and `yielder.yield` are not flagged. Disabled by default - enable with `Tags/MissingYield: Enabled: true`.
10
+ - **[Feature]** New opt-in validator `Documentation/TextSubstitution` detects forbidden characters or strings in YARD documentation and suggests user-defined replacements. Ships with em-dash (—, U+2014) and en-dash (–, U+2013) → hyphen as built-in defaults to catch AI-generated punctuation that projects prefer as plain ASCII hyphens. Fully generic — configure any string-to-string rules via `Substitutions`. Content inside fenced code blocks is skipped. Disabled by default. (#182)
11
+ - **[Feature]** New validator `Documentation/OrphanedDocComment` detects YARD comment blocks with tags (`@param`, `@return`, etc.) that are not attached to any documentable Ruby construct and will be silently dropped by YARD. Triggered when a tagged comment is immediately followed by a non-documentable statement (variable assignment, `require`, `include`, etc.) or sits at end-of-file. Enabled by default. Complementary to `Documentation/BlankLineBeforeDefinition` which handles the blank-lines-before-def case.
12
+ - **[Enhancement]** Each offense now includes a `validator` field with the full config key (e.g. `"Documentation/MissingReturn"`, `"Tags/Order"`) identifying which validator produced it. The text formatter also now displays this path instead of the short offense name, making it easier to locate the right `.yard-lint.yml` setting to adjust.
13
+ - **[Fix]** `Tags/InvalidTypes` no longer reports false positives for YARD's semicolon shorthand in multi-pair fixed-shape Hash types (#171)
14
+ - Types like `Hash{:range => Hash; :severity => Integer; :source, :code, :message => String}` were incorrectly flagged because `;` was not included in the type-name splitter's delimiter set, leaving fragments such as `Hash;` and `Integer;` as tokens that appeared invalid
15
+ - Added `;` to the `extract_type_names` regex so each component is extracted cleanly; invalid types genuinely nested inside semicolon-delimited pairs are still caught
16
+ - **[Enhancement]** Add Ruby warning category opt-in to test helpers
17
+ - **[Fix]** Use `remove_method` instead of `define_singleton_method` to restore `warn` in `InProcessRegistry#capture_warnings`, eliminating a spurious method redefinition warning
18
+
3
19
  ## 1.5.2 (2026-06-02)
4
20
  - **[Fix]** `Tags/InvalidTypes` no longer reports false positives for YARD pseudo-types `undefined`, `unspecified`, and `unknown` (#152)
5
21
  - These lowercase pseudo-types are used in real-world YARD docs (e.g. Solargraph uses `Hash{String => undefined}` extensively) to signal that a type is intentionally unspecified
@@ -11,7 +27,7 @@
11
27
  - Invalid types genuinely nested inside hash values (e.g. `Hash{String => bad_type}`) are still caught and surfaced correctly
12
28
  - **[Enhancement]** `Tags/InvalidTypes` offense messages now name the invalid type(s) and the tag they appear in (#151)
13
29
  - Previously reported a generic `"has at least one tag with an invalid type definition"` message with no further detail
14
- - Now reports `"has invalid type(s): @param body: \`bad_type\`; @return: \`wrong_type\`"` the exact offending type and the tag (including param name for `@param`) where it was found
30
+ - Now reports `"has invalid type(s): @param body: \`bad_type\`; @return: \`wrong_type\`"` - the exact offending type and the tag (including param name for `@param`) where it was found
15
31
  - **[Fix]** Do not flag `@param` on `Struct.new` / `Data.define` constants in `Tags/MeaninglessTag` (#152)
16
32
  - Solargraph uses `@param` annotations on these constants to type the synthesised accessors; flagging them was a false positive
17
33
  - `@option` on these constants is still reported as meaningless
@@ -32,7 +48,7 @@
32
48
  - Both tags are now validated consistently across `Tags/InvalidTypes`, `Tags/TypeSyntax`, `Tags/CollectionType`, and `Tags/NonAsciiType`
33
49
  - **[Fix]** Skip attribute methods in `Tags/ApiTags` validator (#128)
34
50
  - Methods generated by `attr_*`, `@!attribute` directives, `Struct.new`, and `Data.define` are no longer flagged for missing `@api` tags
35
- - YARD's `Data.define` and `Struct.new` handlers hard-replace the generated methods' docstrings, stripping any `@api` tag inherited from the enclosing class, and `@!attribute` directives written above a `Data.define` constant attach to the wrong namespace leaving users with no way to attach per-method `@api` tags to those accessors
51
+ - YARD's `Data.define` and `Struct.new` handlers hard-replace the generated methods' docstrings, stripping any `@api` tag inherited from the enclosing class, and `@!attribute` directives written above a `Data.define` constant attach to the wrong namespace - leaving users with no way to attach per-method `@api` tags to those accessors
36
52
  - Matches the approach taken for `UndocumentedMethodArguments` in #115
37
53
 
38
54
  ## 1.5.1 (2026-04-09)
data/README.md CHANGED
@@ -19,18 +19,18 @@ Accurate documentation isn't just for human developers anymore. [Research shows]
19
19
 
20
20
  YARD-Lint validates your YARD documentation for:
21
21
 
22
- - **Documentation Completeness** - Undocumented classes, modules, methods, parameters, boolean return values, and missing `@return` tags
22
+ - **Documentation Completeness** - Undocumented classes, modules, methods, parameters, boolean return values, and missing `@return` tags; orphaned doc comments with YARD tags that YARD silently drops
23
23
  - **Type Accuracy** - Invalid type definitions, malformed type syntax, non-ASCII characters in types, tuple types, and literal types (symbols, strings, numbers)
24
- - **Tag Validation** - Incorrect tag ordering, meaningless tags, invalid tag positions, unknown tags with suggestions, forbidden tag patterns
24
+ - **Tag Validation** - Incorrect tag ordering, meaningless tags, invalid tag positions, unknown tags with suggestions, forbidden tag patterns, undocumented `yield` calls (opt-in)
25
25
  - **Code Examples** - Syntax validation in `@example` tags, optional style validation with RuboCop/StandardRB
26
26
  - **Semantic Correctness** - Abstract methods with implementations, redundant descriptions
27
- - **Style & Formatting** - Empty comment lines, blank lines before definitions, informal notation patterns, tag group separators
27
+ - **Style & Formatting** - Empty comment lines, blank lines before definitions, informal notation patterns, tag group separators, configurable documentation line length (opt-in)
28
28
  - **Smart Suggestions** - "Did you mean" suggestions for typos in parameter names, tags, and configuration settings
29
29
  - **Configuration Safety** - Validates `.yard-lint.yml` for typos and invalid settings before processing
30
30
  - **Performance** - In-process YARD execution with shared registry (~10x faster than shell-based execution)
31
31
  - **Incremental Adoption** - `--auto-gen-config` generates a baseline todo file to adopt on legacy codebases without fixing everything first
32
32
 
33
- **See the complete list:** [All Features](https://github.com/mensfeld/yard-lint/wiki/Features) | [30 Validators](https://github.com/mensfeld/yard-lint/wiki/Validators)
33
+ **See the complete list:** [All Features](https://github.com/mensfeld/yard-lint/wiki/Features) | [34 Validators](https://github.com/mensfeld/yard-lint/wiki/Validators)
34
34
 
35
35
  ## Installation
36
36
 
@@ -142,6 +142,63 @@ yard-lint lib/ --only Tags/Order,Documentation/UndocumentedObjects
142
142
 
143
143
  **Learn more:** [Advanced Usage Guide](https://github.com/mensfeld/yard-lint/wiki/Advanced-Usage)
144
144
 
145
+ ### Lint from stdin (LSP / Editor Integration)
146
+
147
+ Pass source bytes directly without reading from disk. The `path` argument is still required - it governs config resolution, exclusion matching, and offense location reporting.
148
+
149
+ **CLI:**
150
+
151
+ ```bash
152
+ # Lint source piped through stdin - path is used for config/reporting only
153
+ cat lib/foo.rb | yard-lint --stdin lib/foo.rb
154
+ ```
155
+
156
+ **Ruby API:**
157
+
158
+ ```ruby
159
+ # Lint an in-memory buffer - file does not need to exist on disk
160
+ result = Yard::Lint.run(
161
+ path: 'lib/foo.rb', # used for config, exclusions, and offense locations
162
+ source: editor_buffer_content, # actual source bytes to lint
163
+ progress: false
164
+ )
165
+ ```
166
+
167
+ This is how [solargraph-yard-lint](https://github.com/lekemula/solargraph-yard-lint) surfaces yard-lint offenses on unsaved editor buffers - the file path provides context while the live buffer content is linted directly, matching the behaviour of RuboCop's `--stdin` flag.
168
+
169
+ ### Quickfix Output (Vim / Emacs)
170
+
171
+ Use `--format quickfix` to get one offense per line in the standard `file:line: S: Validator: message` format that editors parse natively for their quickfix/error lists:
172
+
173
+ ```bash
174
+ yard-lint --format quickfix lib/
175
+ ```
176
+
177
+ Example output:
178
+
179
+ ```
180
+ lib/foo.rb:42: E: Documentation/UndocumentedObjects: Class Foo is not documented
181
+ lib/foo.rb:57: W: Tags/InvalidTypes: has invalid type(s): @return: `Sting`
182
+ lib/bar.rb:12: C: Tags/Order: Tags are not in the correct order
183
+ ```
184
+
185
+ **Vim** — set `makeprg` and use `:make` to populate the quickfix list:
186
+
187
+ ```vim
188
+ set makeprg=yard-lint\ --format\ quickfix\ --no-progress\ %
189
+ set errorformat=%f:%l:\ %t:\ %m
190
+ ```
191
+
192
+ Then `:make` lints the current file and `:cnext` / `:cprev` jump between offenses. Note: Vim only recognises `E` and `W` as named types for `:clist E`/`:clist W` filtering — `C` (convention) offenses are stored and navigable but appear without a named type in filtered views.
193
+
194
+ **Emacs** — set `compile-command`:
195
+
196
+ ```elisp
197
+ (setq compile-command "yard-lint --format quickfix --no-progress lib/")
198
+ ```
199
+
200
+ Then `M-x compile` and `M-g n` / `M-g p` navigate between offenses.
201
+
145
202
  ## Configuration Basics
146
203
 
147
204
  Create a `.yard-lint.yml` file in your project root:
@@ -173,10 +230,19 @@ Documentation/UndocumentedObjects:
173
230
  ExcludedMethods:
174
231
  - 'initialize/0'
175
232
  - '/^_/'
233
+ # Skip classes inheriting from these base classes (exact full-path match)
234
+ AllowedParentClasses:
235
+ - StandardError
236
+ - ActiveRecord::Base
176
237
 
177
238
  Documentation/UndocumentedMethodArguments:
178
239
  Enabled: true
179
240
  Severity: warning
241
+ # Skip @param checks for specific methods (exact name, name/arity, /regex/)
242
+ AllowedMethods:
243
+ - call # service objects: call(args) is self-documenting
244
+ - perform # background jobs
245
+ - initialize/1 # only this specific arity of initialize
180
246
 
181
247
  Tags/Order:
182
248
  Enabled: true
@@ -215,6 +281,11 @@ Documentation/MissingReturn:
215
281
  Tags/ExampleStyle:
216
282
  Enabled: true
217
283
  Severity: convention
284
+
285
+ # Opt-in: Enforce max line length in documentation comments
286
+ Documentation/LineLength:
287
+ Enabled: true
288
+ MaxLength: 100
218
289
  ```
219
290
 
220
291
  **Key features:**
@@ -225,6 +296,75 @@ Tags/ExampleStyle:
225
296
 
226
297
  **Learn more:** [Complete Configuration Guide](https://github.com/mensfeld/yard-lint/wiki/Configuration)
227
298
 
299
+ ## Catching Orphaned Documentation Comments
300
+
301
+ YARD silently ignores comment blocks that contain YARD tags (`@param`, `@return`, etc.) when they are not immediately followed by a documentable construct (method, class, module, constant, attribute). This happens with local variable assignments, `require` calls, `include`/`extend` statements, bare `private`/`public` keywords, or comments at the end of a file - the documentation is simply lost with no warning.
302
+
303
+ The `Documentation/OrphanedDocComment` validator (enabled by default) catches this:
304
+
305
+ ```ruby
306
+ # Bad - YARD drops this silently; local variable is not documentable
307
+ # @param name [String] the name
308
+ # @return [void]
309
+ local_var = "value"
310
+
311
+ # Bad - require is not documentable
312
+ # @param id [Integer] user id
313
+ # @return [User]
314
+ require 'some_gem'
315
+
316
+ # Bad - orphaned at end of file
317
+ # @param id [Integer] user id
318
+ # @return [User]
319
+
320
+ # Good - properly attached to a method
321
+ # @param name [String] the name
322
+ # @return [void]
323
+ def greet(name); end
324
+
325
+ # Good - constant assignments are documentable, not flagged
326
+ # @return [Integer] the answer
327
+ ANSWER = 42
328
+ ```
329
+
330
+ This validator is complementary to `Documentation/BlankLineBeforeDefinition` (which handles the case where blank lines separate a doc comment from a `def` - YARD still attaches it despite the gap).
331
+
332
+ ## Documenting yield (opt-in)
333
+
334
+ The `Tags/MissingYield` validator (opt-in, disabled by default) detects methods that call `yield` in their body but do not document the block with a `@yield`, `@yieldparam`, or `@yieldreturn` tag. Callers need to know a method yields in order to pass a block.
335
+
336
+ Enable it in `.yard-lint.yml`:
337
+
338
+ ```yaml
339
+ Tags/MissingYield:
340
+ Enabled: true
341
+ Severity: warning
342
+ ```
343
+
344
+ ```ruby
345
+ # Bad - method yields but block is not documented
346
+ # @param items [Array] the items to process
347
+ def each(items)
348
+ items.each { |item| yield item }
349
+ end
350
+
351
+ # Good - block documented with @yield
352
+ # @param items [Array] the items to process
353
+ # @yield [item] each item in the collection
354
+ def each(items)
355
+ items.each { |item| yield item }
356
+ end
357
+
358
+ # Good - @yieldparam is also accepted
359
+ # @param items [Array] the items to process
360
+ # @yieldparam item [Object] each item
361
+ def each(items)
362
+ items.each { |item| yield item }
363
+ end
364
+ ```
365
+
366
+ Method calls like `Fiber.yield` and `yielder.yield` (Enumerator::Yielder) are not flagged - only the `yield` keyword triggers the check.
367
+
228
368
  ## Handling Non-Standard Types
229
369
 
230
370
  By default `Tags/InvalidTypes` accepts all built-in Ruby classes, constants, and a set of YARD pseudo-types (`nil`, `true`, `false`, `self`, `void`, `undefined`, `unspecified`, `unknown`). If your project uses additional type names that are not real Ruby classes - project-specific aliases, LSP extensions, or informal conventions - you can declare them via `ExtraTypes` so yard-lint does not report them as `InvalidTagType` offenses.
@@ -294,7 +434,7 @@ jobs:
294
434
  ```bash
295
435
  #!/bin/bash
296
436
  # .git/hooks/pre-commit
297
- bundle exec yard-lint lib/ --staged --fail-on-severity error
437
+ bundle exec yard-lint lib/ --staged
298
438
  ```
299
439
 
300
440
  ### GitLab CI
@@ -320,10 +460,11 @@ Configuration:
320
460
  -c, --config FILE Path to config file (default: .yard-lint.yml)
321
461
 
322
462
  Output:
323
- -f, --format FORMAT Output format (text, json)
463
+ -f, --format FORMAT Output format (text, json, quickfix)
324
464
  -q, --quiet Quiet mode (only show summary)
325
465
  --stats Show documentation coverage statistics
326
466
  --[no-]progress Show progress indicator (default: auto-detect TTY)
467
+ --stdin Read source from stdin; PATH still required for config/reporting
327
468
 
328
469
  Coverage:
329
470
  --min-coverage N Minimum documentation coverage required (0-100)
@@ -352,6 +493,23 @@ Information:
352
493
 
353
494
  **Learn more:** [Advanced Usage](https://github.com/mensfeld/yard-lint/wiki/Advanced-Usage) - CLI reference, JSON output, coverage
354
495
 
496
+ ## Offense Structure
497
+
498
+ Every offense (in text, JSON, and quickfix output) includes a `validator` field with the full config key that produced it, making it easy to find the right `.yard-lint.yml` setting to adjust:
499
+
500
+ ```json
501
+ {
502
+ "name": "OrphanedDocComment",
503
+ "validator": "Documentation/OrphanedDocComment",
504
+ "severity": "warning",
505
+ "message": "Documentation comment with @param, @return is orphaned - YARD will ignore it",
506
+ "location": "lib/my_class.rb",
507
+ "location_line": 42
508
+ }
509
+ ```
510
+
511
+ The text formatter also shows the validator path (e.g., `[Documentation/OrphanedDocComment]`) instead of just the short offense name.
512
+
355
513
  ## Documentation
356
514
 
357
515
  ### Quick Links
@@ -359,7 +517,7 @@ Information:
359
517
  - **[Wiki Home](https://github.com/mensfeld/yard-lint/wiki)** - Full documentation
360
518
  - **[Installation](https://github.com/mensfeld/yard-lint/wiki/Installation)** - Installation guide
361
519
  - **[Configuration](https://github.com/mensfeld/yard-lint/wiki/Configuration)** - Complete configuration reference
362
- - **[Validators](https://github.com/mensfeld/yard-lint/wiki/Validators)** - All 30 validators documented
520
+ - **[Validators](https://github.com/mensfeld/yard-lint/wiki/Validators)** - All 34 validators documented
363
521
  - **[Features](https://github.com/mensfeld/yard-lint/wiki/Features)** - All features explained
364
522
 
365
523
  ### Workflows
data/bin/yard-lint CHANGED
@@ -17,7 +17,7 @@ OptionParser.new do |opts|
17
17
  config_file = file
18
18
  end
19
19
 
20
- opts.on('-f', '--format FORMAT', 'Output format (text, json)') do |format|
20
+ opts.on('-f', '--format FORMAT', 'Output format (text, json, quickfix)') do |format|
21
21
  options[:format] = format
22
22
  end
23
23
 
@@ -37,6 +37,10 @@ OptionParser.new do |opts|
37
37
  options[:progress] = value
38
38
  end
39
39
 
40
+ opts.on('--stdin', 'Read source from stdin; PATH argument is required for config/reporting') do
41
+ options[:stdin] = true
42
+ end
43
+
40
44
  opts.separator ''
41
45
  opts.separator 'Diff mode options (mutually exclusive):'
42
46
 
@@ -111,12 +115,14 @@ OptionParser.new do |opts|
111
115
  puts ' yard-lint --staged # Lint only staged files'
112
116
  puts ' yard-lint --changed # Lint only uncommitted files'
113
117
  puts ' yard-lint --format json lib/ # Output in JSON format'
118
+ puts ' yard-lint --format quickfix lib/ # Output in quickfix format (Vim/Emacs)'
114
119
  puts ' yard-lint --init # Generate default config'
115
120
  puts ' yard-lint --init --strict # Generate strict config'
116
121
  puts ' yard-lint --update # Update config with new validators'
117
122
  puts ' yard-lint --auto-gen-config # Generate todo file for existing violations'
118
123
  puts ' yard-lint --regenerate-todo # Regenerate todo file'
119
124
  puts ' yard-lint --auto-gen-config --exclude-limit 10 # Custom grouping threshold'
125
+ puts ' cat lib/foo.rb | yard-lint --stdin lib/foo.rb # Lint source piped from stdin'
120
126
  exit
121
127
  end
122
128
  end.parse!
@@ -199,6 +205,16 @@ end
199
205
  # Get path argument (defaults to current directory)
200
206
  path = ARGV[0] || '.'
201
207
 
208
+ # Read stdin source when --stdin flag is given
209
+ stdin_source = nil
210
+ if options[:stdin]
211
+ if path == '.' || File.directory?(path)
212
+ puts 'Error: --stdin requires an explicit file path argument (e.g. yard-lint --stdin lib/foo.rb)'
213
+ exit 1
214
+ end
215
+ stdin_source = $stdin.read
216
+ end
217
+
202
218
  # Clear YARD registry to ensure fresh run on each CLI invocation
203
219
  YARD::Registry.clear
204
220
 
@@ -235,14 +251,21 @@ if options[:only]
235
251
  config.only_validators = options[:only]
236
252
  end
237
253
 
254
+ # Suppress progress for quickfix to avoid contaminating machine-readable output
255
+ options[:progress] = false if options[:format] == 'quickfix' && options[:progress].nil?
256
+
238
257
  # Run the linter
239
258
  begin
240
259
  result = Yard::Lint.run(
241
260
  path: path,
242
261
  config: config,
243
262
  progress: options[:progress],
244
- diff: diff_mode
263
+ diff: diff_mode,
264
+ source: stdin_source
245
265
  )
266
+ rescue ArgumentError => e
267
+ puts "Error: #{e.message}"
268
+ exit 1
246
269
  rescue Yard::Lint::Git::Error => e
247
270
  puts "Git error: #{e.message}"
248
271
  exit 1
@@ -251,6 +274,11 @@ rescue Yard::Lint::Errors::FileNotFoundError => e
251
274
  exit 1
252
275
  end
253
276
 
277
+ # Maps a severity string to its single-character quickfix/display code
278
+ severity_char = lambda do |s|
279
+ { 'error' => 'E', 'warning' => 'W', 'convention' => 'C', 'never' => 'C' }.fetch(s.to_s, '?')
280
+ end
281
+
254
282
  # Format and display results
255
283
  case options[:format]
256
284
  when 'json'
@@ -260,6 +288,19 @@ when 'json'
260
288
  offenses: result.offenses
261
289
  })
262
290
  exit result.exit_code
291
+ when 'quickfix'
292
+ if config.min_coverage
293
+ coverage = result.documentation_coverage
294
+ if coverage && coverage[:coverage] < config.min_coverage
295
+ warn "Error: Documentation coverage #{coverage[:coverage].round(2)}% is below minimum #{config.min_coverage}%"
296
+ end
297
+ end
298
+ result.offenses.each do |offense|
299
+ char = severity_char.call(offense[:severity])
300
+ message = offense[:message].to_s.gsub(/\n/, ' ')
301
+ puts "#{offense[:location]}:#{offense[:location_line]}: #{char}: #{offense[:validator]}: #{message}"
302
+ end
303
+ exit result.exit_code
263
304
  when 'text', nil
264
305
  # Calculate coverage stats if requested or configured
265
306
  coverage = result.documentation_coverage if options[:stats] || options[:min_coverage] || config.min_coverage
@@ -306,15 +347,8 @@ when 'text', nil
306
347
  puts "Found #{result.count} offense(s):\n\n"
307
348
 
308
349
  result.offenses.each do |offense|
309
- severity_symbol = case offense[:severity]
310
- when 'error' then 'E'
311
- when 'warning' then 'W'
312
- when 'convention' then 'C'
313
- else '?'
314
- end
315
-
316
- puts "[#{severity_symbol}] #{offense[:location]}:#{offense[:location_line]}"
317
- puts " #{offense[:name]}: #{offense[:message]}"
350
+ puts "[#{severity_char.call(offense[:severity])}] #{offense[:location]}:#{offense[:location_line]}"
351
+ puts " #{offense[:validator]}: #{offense[:message]}"
318
352
  puts
319
353
  end
320
354
  end
@@ -21,8 +21,10 @@ module Yard
21
21
  # Parse Ruby source files and populate the YARD registry.
22
22
  # Captures any warnings emitted by YARD during parsing for later dispatch.
23
23
  # @param files [Array<String>] absolute or relative paths to Ruby source files
24
+ # @param source [String, nil] in-memory source; when given, `files.first` is used
25
+ # as the virtual filename for registry/location reporting only
24
26
  # @return [void]
25
- def parse(files)
27
+ def parse(files, source: nil)
26
28
  @mutex.synchronize do
27
29
  return if @parsed
28
30
 
@@ -33,16 +35,28 @@ module Yard
33
35
  original_level = YARD::Logger.instance.level
34
36
  YARD::Logger.instance.level = 4 # Only show fatal errors
35
37
 
36
- # First pass: parse all files to process directive definitions
37
- YARD.parse(files)
38
-
39
- # Clear checksums to force reparsing without clearing the registry.
40
- # This allows macro definitions from the first pass to be available
41
- # during the second pass, enabling proper directive expansion regardless of parse order.
42
- YARD::Registry.checksums.clear
38
+ if source
39
+ virtual_path = files.first
40
+ # First pass: register directive/macro definitions from the in-memory source.
41
+ # We set parser.file manually so registered objects carry the virtual path.
42
+ parse_source_string(source, virtual_path)
43
+ # Clear checksums so the second pass is not skipped
44
+ YARD::Registry.checksums.clear
45
+ # Second pass: full parse with all directives available
46
+ @warnings = capture_warnings { parse_source_string(source, virtual_path) }
47
+ else
48
+ # First pass: parse all files to process directive definitions
49
+ YARD.parse(files)
50
+
51
+ # Clear checksums to force reparsing without clearing the registry.
52
+ # This allows macro definitions from the first pass to be available
53
+ # during the second pass, enabling proper directive expansion regardless of parse order.
54
+ YARD::Registry.checksums.clear
55
+
56
+ # Second pass: reparse files now that all directive definitions are available
57
+ @warnings = capture_warnings { YARD.parse(files) }
58
+ end
43
59
 
44
- # Second pass: reparse files now that all directive definitions are available
45
- @warnings = capture_warnings { YARD.parse(files) }
46
60
  @parsed = true
47
61
 
48
62
  YARD::Logger.instance.level = original_level
@@ -110,6 +124,18 @@ module Yard
110
124
 
111
125
  private
112
126
 
127
+ # Parse Ruby source from a string and register objects under a virtual path.
128
+ # YARD::Parser::SourceParser#parse accepts a StringIO but keeps @file as '(stdin)'
129
+ # unless we set it explicitly before parsing.
130
+ # @param source [String] Ruby source code to parse
131
+ # @param virtual_path [String] filename to assign to registered objects
132
+ # @return [void]
133
+ def parse_source_string(source, virtual_path)
134
+ parser = YARD::Parser::SourceParser.new(:ruby)
135
+ parser.file = virtual_path
136
+ parser.parse(StringIO.new(source))
137
+ end
138
+
113
139
  # Capture warnings during a block execution
114
140
  # @yield Block to execute while capturing warnings
115
141
  # @return [Array<String>] captured warnings
@@ -130,8 +156,8 @@ module Yard
130
156
 
131
157
  captured
132
158
  ensure
133
- # Restore original warn method
134
- YARD::Logger.instance.define_singleton_method(:warn, original_warn) if original_warn
159
+ sc = YARD::Logger.instance.singleton_class
160
+ sc.remove_method(:warn) if sc.public_instance_methods(false).include?(:warn)
135
161
  end
136
162
  end
137
163
  end
@@ -96,6 +96,8 @@ module Yard
96
96
  # @return [String] validator name for config lookup
97
97
  def validator_name
98
98
  # Extract from class path: Validators::Tags::Order::Result => 'Tags/Order'
99
+ return '' unless self.class.name
100
+
99
101
  parts = self.class.name.split('::')
100
102
  validators_index = parts.index('Validators')
101
103
  return '' unless validators_index
@@ -125,6 +127,7 @@ module Yard
125
127
  severity: configured_severity,
126
128
  type: self.class.offense_type,
127
129
  name: computed_offense_name,
130
+ validator: validator_name,
128
131
  message: build_message(offense_data),
129
132
  location: offense_data[:location] || offense_data[:file],
130
133
  location_line: offense_data[:line] || offense_data[:location_line] || 0
@@ -15,9 +15,11 @@ module Yard
15
15
 
16
16
  # @param selection [Array<String>] array with ruby files to check
17
17
  # @param config [Yard::Lint::Config] configuration object
18
- def initialize(selection, config = Config.new)
18
+ # @param source [String, nil] in-memory source content (overrides disk reads)
19
+ def initialize(selection, config = Config.new, source: nil)
19
20
  @selection = Array(selection).flatten
20
21
  @config = config
22
+ @source = source
21
23
  @result_builder = ResultBuilder.new(config)
22
24
  @progress_formatter = nil
23
25
  end
@@ -45,7 +47,7 @@ module Yard
45
47
 
46
48
  # Initialize in-process infrastructure
47
49
  registry = Executor::InProcessRegistry.new
48
- registry.parse(selection)
50
+ registry.parse(selection, source: @source)
49
51
 
50
52
  query_executor = Executor::QueryExecutor.new(registry)
51
53
  warning_dispatcher = Executor::WarningDispatcher.new
@@ -36,21 +36,34 @@ Documentation/UndocumentedObjects:
36
36
  ExcludedMethods:
37
37
  - 'initialize/0' # Exclude parameter-less initialize
38
38
  - '/^_/' # Exclude private methods (by convention)
39
+ # AllowedParentClasses:
40
+ # - StandardError # Skip error subclasses
41
+ # - ApplicationRecord # Skip Rails model subclasses
39
42
 
40
43
  Documentation/UndocumentedMethodArguments:
41
44
  Description: 'Checks for method parameters without @param tags.'
42
45
  Enabled: true
43
46
  Severity: warning
47
+ # AllowedParentClasses:
48
+ # - StandardError
49
+ # AllowedMethods: skip @param checks for specific methods (exact name, name/arity, /regex/)
50
+ # - call
51
+ # - perform
52
+ # - initialize/1
44
53
 
45
54
  Documentation/UndocumentedBooleanMethods:
46
55
  Description: 'Checks that question mark methods document their boolean return.'
47
56
  Enabled: true
48
57
  Severity: warning
58
+ # AllowedParentClasses:
59
+ # - StandardError
49
60
 
50
61
  Documentation/UndocumentedOptions:
51
62
  Description: 'Detects methods with options hash parameters but no @option tags.'
52
63
  Enabled: true
53
64
  Severity: warning
65
+ # AllowedParentClasses:
66
+ # - StandardError
54
67
 
55
68
  Documentation/MissingReturn:
56
69
  Description: 'Requires @return tags on all methods (opt-in for strict documentation).'
@@ -59,6 +72,13 @@ Documentation/MissingReturn:
59
72
  ExcludedMethods:
60
73
  - 'initialize' # Exclude all initialize methods
61
74
  # - '/^_/' # Uncomment to exclude private methods (by convention)
75
+ # AllowedParentClasses:
76
+ # - StandardError
77
+
78
+ Documentation/OrphanedDocComment:
79
+ Description: 'Detects YARD comment blocks with tags not attached to any documentable construct.'
80
+ Enabled: true
81
+ Severity: warning
62
82
 
63
83
  Documentation/MarkdownSyntax:
64
84
  Description: 'Detects common markdown syntax errors in documentation.'
@@ -82,6 +102,20 @@ Documentation/BlankLineBeforeDefinition:
82
102
  SingleBlankLine: true
83
103
  OrphanedDocs: true
84
104
 
105
+ Documentation/LineLength:
106
+ Description: 'Detects documentation comment lines that exceed the configured maximum length.'
107
+ Enabled: false # Opt-in validator
108
+ Severity: convention
109
+ MaxLength: 120
110
+
111
+ Documentation/TextSubstitution:
112
+ Description: 'Detects forbidden characters or strings in documentation and suggests replacements.'
113
+ Enabled: false # Opt-in validator
114
+ Severity: warning
115
+ Substitutions:
116
+ "—": "-" # em-dash (U+2014)
117
+ "–": "-" # en-dash (U+2013)
118
+
85
119
  # Tags validators
86
120
  Tags/Order:
87
121
  Description: 'Enforces consistent ordering of YARD tags.'
@@ -131,6 +165,11 @@ Tags/MeaninglessTag:
131
165
  - module
132
166
  - constant
133
167
 
168
+ Tags/MissingYield:
169
+ Description: 'Detects methods that yield to a block without a @yield, @yieldparam, or @yieldreturn tag.'
170
+ Enabled: false # Opt-in validator
171
+ Severity: warning
172
+
134
173
  Tags/CollectionType:
135
174
  Description: 'Validates Hash collection syntax consistency.'
136
175
  Enabled: true
@@ -205,6 +244,7 @@ Tags/RedundantParamDescription:
205
244
  - element
206
245
  EnabledPatterns:
207
246
  ArticleParam: true
247
+ ArticleParamPhrase: true
208
248
  PossessiveParam: true
209
249
  TypeRestatement: true
210
250
  ParamToVerb: true