yard-lint 1.5.1 → 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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -0
  3. data/README.md +207 -8
  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 +11 -3
  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/todo_generator.rb +6 -4
  11. data/lib/yard/lint/validators/base.rb +125 -0
  12. data/lib/yard/lint/validators/documentation/line_length/config.rb +21 -0
  13. data/lib/yard/lint/validators/documentation/line_length/messages_builder.rb +26 -0
  14. data/lib/yard/lint/validators/documentation/line_length/parser.rb +65 -0
  15. data/lib/yard/lint/validators/documentation/line_length/result.rb +26 -0
  16. data/lib/yard/lint/validators/documentation/line_length/validator.rb +59 -0
  17. data/lib/yard/lint/validators/documentation/line_length.rb +43 -0
  18. data/lib/yard/lint/validators/documentation/missing_return/config.rb +2 -1
  19. data/lib/yard/lint/validators/documentation/missing_return/parser.rb +0 -1
  20. data/lib/yard/lint/validators/documentation/missing_return/validator.rb +1 -0
  21. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/config.rb +20 -0
  22. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/messages_builder.rb +23 -0
  23. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/parser.rb +38 -0
  24. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/result.rb +24 -0
  25. data/lib/yard/lint/validators/documentation/orphaned_doc_comment/validator.rb +121 -0
  26. data/lib/yard/lint/validators/documentation/orphaned_doc_comment.rb +53 -0
  27. data/lib/yard/lint/validators/documentation/text_substitution/config.rb +24 -0
  28. data/lib/yard/lint/validators/documentation/text_substitution/messages_builder.rb +33 -0
  29. data/lib/yard/lint/validators/documentation/text_substitution/parser.rb +57 -0
  30. data/lib/yard/lint/validators/documentation/text_substitution/result.rb +24 -0
  31. data/lib/yard/lint/validators/documentation/text_substitution/validator.rb +72 -0
  32. data/lib/yard/lint/validators/documentation/text_substitution.rb +55 -0
  33. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/config.rb +2 -1
  34. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +1 -0
  35. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/config.rb +3 -1
  36. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +3 -1
  37. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +1 -2
  38. data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
  39. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +1 -0
  40. data/lib/yard/lint/validators/documentation/undocumented_options/config.rb +2 -1
  41. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +1 -0
  42. data/lib/yard/lint/validators/documentation/undocumented_options.rb +1 -2
  43. data/lib/yard/lint/validators/tags/api_tags/result.rb +1 -0
  44. data/lib/yard/lint/validators/tags/api_tags/validator.rb +12 -0
  45. data/lib/yard/lint/validators/tags/collection_type/config.rb +1 -1
  46. data/lib/yard/lint/validators/tags/collection_type/validator.rb +1 -3
  47. data/lib/yard/lint/validators/tags/example_style/result.rb +1 -0
  48. data/lib/yard/lint/validators/tags/example_syntax/result.rb +1 -0
  49. data/lib/yard/lint/validators/tags/invalid_types/config.rb +1 -1
  50. data/lib/yard/lint/validators/tags/invalid_types/messages_builder.rb +14 -3
  51. data/lib/yard/lint/validators/tags/invalid_types/parser.rb +63 -3
  52. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +31 -16
  53. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +16 -1
  54. data/lib/yard/lint/validators/tags/missing_yield/config.rb +20 -0
  55. data/lib/yard/lint/validators/tags/missing_yield/messages_builder.rb +22 -0
  56. data/lib/yard/lint/validators/tags/missing_yield/parser.rb +39 -0
  57. data/lib/yard/lint/validators/tags/missing_yield/result.rb +24 -0
  58. data/lib/yard/lint/validators/tags/missing_yield/validator.rb +76 -0
  59. data/lib/yard/lint/validators/tags/missing_yield.rb +56 -0
  60. data/lib/yard/lint/validators/tags/non_ascii_type/config.rb +1 -1
  61. data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +1 -3
  62. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +4 -2
  63. data/lib/yard/lint/validators/tags/redundant_param_description.rb +2 -1
  64. data/lib/yard/lint/validators/tags/type_syntax/config.rb +1 -1
  65. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +1 -3
  66. data/lib/yard/lint/validators/tags/type_syntax.rb +1 -2
  67. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/parser.rb +4 -4
  68. data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +1 -2
  69. data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +1 -2
  70. data/lib/yard/lint/version.rb +1 -1
  71. data/lib/yard/lint.rb +14 -4
  72. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88a6ef4d1499a08d7c1776c73605011fd32af9c7afc3f473e4e2c000b2f1d4b6
4
- data.tar.gz: a4002c074922df07b82d963f8844745bdd925087b1f6f4b2ba87af10dab1e523
3
+ metadata.gz: ef1425a1be17cac04595b6558a742eb4036fc1cc66891e1d8f31eb46094c8914
4
+ data.tar.gz: d5ba0ef812eb0261718d7259d2da0b0f0b2fa37a1674893f6d7d3178be3bf551
5
5
  SHA512:
6
- metadata.gz: 4c7cd92f75e9b839a90340241288966f157c50ba2d82bea1a50892f70b6d62493e81d2f60c4a147616adeada79f65e9ed3ad00fddc6353a9229ec705073ae1c2
7
- data.tar.gz: ce364d5460ee86d59e058a78e3d7c72f6d76f32b96279436dd71f9f50b0ff4fb7e16e2bc60a06542642a337707df0df6e8d1e798b0c5282d7f350a3f39a38680
6
+ metadata.gz: 23d60c91521b5afbadd0a43380682686f7b10aa68a968f5091da2de425f477f71ce3b8a6477ca5d0197d7138979dffc4a0aebe2582b3a64cd4092b18b6222c95
7
+ data.tar.gz: 966fe65d5e72ba1dbf225fe8802f2259dcdb436a66b2571d6caf7aff7d59491e1b337c7fe4db0e68354bceebb192c89d40ed6c1229b5e2bd2f75d2f5cbde456a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
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
+
19
+ ## 1.5.2 (2026-06-02)
20
+ - **[Fix]** `Tags/InvalidTypes` no longer reports false positives for YARD pseudo-types `undefined`, `unspecified`, and `unknown` (#152)
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
22
+ - They are now treated as valid types alongside the existing `nil`, `void`, `self`, `true`, `false` defaults
23
+ - **[Fix]** `Tags/InvalidTypes` no longer reports false positives for string literal hash keys (e.g. `Hash{"to" => String}`) (#152)
24
+ - String literal keys like `"email"` or `"name"` are valid YARD hash key notation and are now treated as valid alongside the existing symbol literal support (`:key`)
25
+ - **[Fix]** `Tags/InvalidTypes` no longer reports false positives for nested `Hash{K => V}` types (#151, #152)
26
+ - Complex hash types such as `Hash{String => Hash{Symbol => Array<String>}}` were occasionally flagged as invalid; the `extract_type_names` splitter already handled them correctly after the 1.5.2 sanitizer rewrite, and regression tests now lock that behaviour in
27
+ - Invalid types genuinely nested inside hash values (e.g. `Hash{String => bad_type}`) are still caught and surfaced correctly
28
+ - **[Enhancement]** `Tags/InvalidTypes` offense messages now name the invalid type(s) and the tag they appear in (#151)
29
+ - Previously reported a generic `"has at least one tag with an invalid type definition"` message with no further detail
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
31
+ - **[Fix]** Do not flag `@param` on `Struct.new` / `Data.define` constants in `Tags/MeaninglessTag` (#152)
32
+ - Solargraph uses `@param` annotations on these constants to type the synthesised accessors; flagging them was a false positive
33
+ - `@option` on these constants is still reported as meaningless
34
+ - **[Fix]** Fix `--auto-gen-config` crash (`NoMethodError`) when a YARD warning is emitted without file context (#150)
35
+ - A `@!macro [new]` body with an invalid tag format (e.g. `@return name [Type]`) that is also referenced inside the defining method's body causes YARD to expand the macro with `object=nil`, emitting `Invalid tag format` without a file path → `offense[:location] = nil` → `make_relative_path(nil)` → crash
36
+ - `TodoGenerator#make_relative_path` now returns `nil` for `nil` input; `run_linting` uses `filter_map` to skip nil locations when building exclusion lists
37
+ - `Runner#filter_result_offenses` now always drops nil-location offenses before exclusion-pattern filtering (they would otherwise appear as `:0` in output)
38
+ - Fix `Warnings/DuplicatedParameterName::Parser`: was inheriting from `OneLineBase` despite YARD emitting the warning across two lines (same format as `UnknownParameterName`), causing `offense[:location]` to always be `nil`; switched to `TwoLineBase` and corrected the location regex to match YARD's backtick-open/single-quote-close path format
39
+ - **[Fix]** Stop false positives for allowed defaults (`self`, `nil`, `true`, `false`, `void`) inside generic types in `Tags/InvalidTypes` validator
40
+ - Types like `Array<self>`, `Hash{Symbol => nil}`, `Array<true>` were incorrectly flagged as `InvalidTagType` because the sanitize logic concatenated type components (e.g., `Array<self>` became `Arrayself`, which is not in `ALLOWED_DEFAULTS`)
41
+ - Replaced the `tr`-based sanitizer with a proper type name splitter that checks each component individually
42
+ - **[Fix]** Validate types inside `@overload` blocks across all type validators (`Tags/TypeSyntax`, `Tags/InvalidTypes`, `Tags/CollectionType`, `Tags/NonAsciiType`)
43
+ - YARD stores `@overload` inner tags on the overload's own docstring, making them invisible to validators that only traverse `object.docstring.tags`
44
+ - Added `all_typed_tags` helper to `Validators::Base` that collects matching tags from both the docstring and any `@overload` blocks
45
+ - **[Fix]** Add `@raise` and `@yieldparam` to `ValidatedTags` in all type validators
46
+ - `@raise` tag types (e.g., `@raise [ArgumentError]`) were never validated for type syntax or correctness by any validator
47
+ - `@yieldparam` was missing from `Tags/InvalidTypes`, `Tags/TypeSyntax`, and `Tags/CollectionType` (only `Tags/NonAsciiType` included it)
48
+ - Both tags are now validated consistently across `Tags/InvalidTypes`, `Tags/TypeSyntax`, `Tags/CollectionType`, and `Tags/NonAsciiType`
49
+ - **[Fix]** Skip attribute methods in `Tags/ApiTags` validator (#128)
50
+ - Methods generated by `attr_*`, `@!attribute` directives, `Struct.new`, and `Data.define` are no longer flagged for missing `@api` tags
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
52
+ - Matches the approach taken for `UndocumentedMethodArguments` in #115
53
+
3
54
  ## 1.5.1 (2026-04-09)
4
55
  - **[Fix]** Remove `mise.toml` and `proxy_types` from the repository and exclude non-production files (`.ruby-version`, `Rakefile`, `misc/`, `renovate.json`, `package.json`, `package-lock.json`, lock files) from RubyGems releases
5
56
 
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
@@ -197,8 +263,11 @@ Tags/Order:
197
263
  Tags/InvalidTypes:
198
264
  Enabled: true
199
265
  Severity: warning
266
+ # ExtraTypes: declare non-standard type names that should not be flagged.
267
+ # Useful for project aliases, LSP extensions (e.g. Solargraph's `generic`),
268
+ # or any informal type name your team uses in YARD docs.
200
269
  ExtraTypes:
201
- - CustomType
270
+ - generic # Solargraph generic type parameter (lsegal/yard#1683)
202
271
  - MyNamespace::CustomType
203
272
 
204
273
  # Opt-in: Require @return tags on all methods
@@ -212,6 +281,11 @@ Documentation/MissingReturn:
212
281
  Tags/ExampleStyle:
213
282
  Enabled: true
214
283
  Severity: convention
284
+
285
+ # Opt-in: Enforce max line length in documentation comments
286
+ Documentation/LineLength:
287
+ Enabled: true
288
+ MaxLength: 100
215
289
  ```
216
290
 
217
291
  **Key features:**
@@ -222,6 +296,113 @@ Tags/ExampleStyle:
222
296
 
223
297
  **Learn more:** [Complete Configuration Guide](https://github.com/mensfeld/yard-lint/wiki/Configuration)
224
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
+
368
+ ## Handling Non-Standard Types
369
+
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.
371
+
372
+ ### Project-Specific Type Aliases
373
+
374
+ ```yaml
375
+ Tags/InvalidTypes:
376
+ ExtraTypes:
377
+ - Callable # informal "anything that responds to #call"
378
+ - Awaitable # async type alias used across the project
379
+ - Result # dry-monad-style Result type used in prose docs
380
+ ```
381
+
382
+ ### LSP / Tool-Specific Extensions
383
+
384
+ [Solargraph](https://solargraph.org) supports a `generic` type-parameter notation (e.g. `Hash{Class<generic<T>> => Set<generic<T>>}`) that is [proposed for adoption](https://github.com/lsegal/yard/issues/1683) but not yet part of the YARD standard. Until it is, add it to `ExtraTypes` to prevent false positives:
385
+
386
+ ```yaml
387
+ Tags/InvalidTypes:
388
+ ExtraTypes:
389
+ - generic
390
+ ```
391
+
392
+ ### Built-In Pseudo-Types (no configuration needed)
393
+
394
+ The following lowercase YARD pseudo-types are accepted out of the box and do **not** need to be listed in `ExtraTypes`:
395
+
396
+ | Type | Meaning |
397
+ |------|---------|
398
+ | `nil` | Explicitly nil |
399
+ | `true` / `false` | Boolean literals |
400
+ | `self` | Returns the receiver |
401
+ | `void` | No meaningful return value |
402
+ | `undefined` | Type is intentionally unspecified (used by Solargraph) |
403
+ | `unspecified` | Alias for undefined |
404
+ | `unknown` | Type is unknown |
405
+
225
406
  ## CI Integration
226
407
 
227
408
  ### GitHub Actions
@@ -253,7 +434,7 @@ jobs:
253
434
  ```bash
254
435
  #!/bin/bash
255
436
  # .git/hooks/pre-commit
256
- bundle exec yard-lint lib/ --staged --fail-on-severity error
437
+ bundle exec yard-lint lib/ --staged
257
438
  ```
258
439
 
259
440
  ### GitLab CI
@@ -279,10 +460,11 @@ Configuration:
279
460
  -c, --config FILE Path to config file (default: .yard-lint.yml)
280
461
 
281
462
  Output:
282
- -f, --format FORMAT Output format (text, json)
463
+ -f, --format FORMAT Output format (text, json, quickfix)
283
464
  -q, --quiet Quiet mode (only show summary)
284
465
  --stats Show documentation coverage statistics
285
466
  --[no-]progress Show progress indicator (default: auto-detect TTY)
467
+ --stdin Read source from stdin; PATH still required for config/reporting
286
468
 
287
469
  Coverage:
288
470
  --min-coverage N Minimum documentation coverage required (0-100)
@@ -311,6 +493,23 @@ Information:
311
493
 
312
494
  **Learn more:** [Advanced Usage](https://github.com/mensfeld/yard-lint/wiki/Advanced-Usage) - CLI reference, JSON output, coverage
313
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
+
314
513
  ## Documentation
315
514
 
316
515
  ### Quick Links
@@ -318,7 +517,7 @@ Information:
318
517
  - **[Wiki Home](https://github.com/mensfeld/yard-lint/wiki)** - Full documentation
319
518
  - **[Installation](https://github.com/mensfeld/yard-lint/wiki/Installation)** - Installation guide
320
519
  - **[Configuration](https://github.com/mensfeld/yard-lint/wiki/Configuration)** - Complete configuration reference
321
- - **[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
322
521
  - **[Features](https://github.com/mensfeld/yard-lint/wiki/Features)** - All features explained
323
522
 
324
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
@@ -108,12 +110,18 @@ module Yard
108
110
  end
109
111
 
110
112
  # Filter result offenses based on per-validator exclusions
111
- # Removes offenses where the file path matches exclusion patterns
113
+ # Also always removes offenses with no file location (e.g. YARD warnings
114
+ # emitted without file context such as from macro expansion with nil object)
115
+ # since they carry no actionable information and would display as `:0`.
112
116
  # @param validator_name [String] full validator name
113
117
  # @param result [Results::Base] result object with offenses
114
118
  # @return [Results::Base, nil] result with filtered offenses, or nil if no offenses remain
115
119
  def filter_result_offenses(validator_name, result)
116
120
  validator_excludes = config.validator_all_excludes(validator_name)
121
+
122
+ # Drop offenses with no file location regardless of exclusion config
123
+ result.offenses = result.offenses.reject { |o| (o[:location] || o[:file]).nil? }
124
+ return nil if result.offenses.empty?
117
125
  return result if validator_excludes.empty?
118
126
 
119
127
  working_dir = Dir.pwd