yard-lint 1.6.1 → 1.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -1
- data/README.md +4 -3
- data/bin/yard-lint +35 -4
- data/lib/yard/lint/config.rb +21 -4
- data/lib/yard/lint/config_loader.rb +31 -6
- data/lib/yard/lint/config_updater.rb +22 -1
- data/lib/yard/lint/config_validator.rb +2 -1
- data/lib/yard/lint/executor/in_process_registry.rb +58 -23
- data/lib/yard/lint/executor/warning_dispatcher.rb +1 -0
- data/lib/yard/lint/git.rb +44 -3
- data/lib/yard/lint/path_grouper.rb +4 -1
- data/lib/yard/lint/results/aggregate.rb +15 -7
- data/lib/yard/lint/stats_calculator.rb +8 -2
- data/lib/yard/lint/templates/default_config.yml +34 -1
- data/lib/yard/lint/templates/strict_config.yml +34 -1
- data/lib/yard/lint/todo_generator.rb +35 -14
- data/lib/yard/lint/validators/base.rb +39 -1
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/validator.rb +17 -2
- data/lib/yard/lint/validators/documentation/empty_comment_line/validator.rb +5 -2
- data/lib/yard/lint/validators/documentation/line_length/validator.rb +1 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +66 -9
- data/lib/yard/lint/validators/documentation/missing_return/validator.rb +3 -3
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment/validator.rb +79 -11
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment.rb +4 -4
- data/lib/yard/lint/validators/documentation/text_substitution/validator.rb +10 -2
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/config.rb +10 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/parser.rb +18 -4
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +38 -4
- data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +6 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +30 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +1 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +43 -4
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +11 -1
- data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +36 -5
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +10 -6
- data/lib/yard/lint/validators/tags/example_style/validator.rb +1 -1
- data/lib/yard/lint/validators/tags/example_syntax/config.rb +6 -1
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +74 -3
- data/lib/yard/lint/validators/tags/forbidden_tags/validator.rb +7 -3
- data/lib/yard/lint/validators/tags/informal_notation/config.rb +6 -1
- data/lib/yard/lint/validators/tags/informal_notation/validator.rb +24 -3
- data/lib/yard/lint/validators/tags/invalid_types/config.rb +7 -1
- data/lib/yard/lint/validators/tags/invalid_types/parser.rb +22 -5
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +24 -10
- data/lib/yard/lint/validators/tags/missing_yield/validator.rb +44 -4
- data/lib/yard/lint/validators/tags/missing_yield.rb +8 -8
- data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +13 -1
- data/lib/yard/lint/validators/tags/option_tags/result.rb +1 -0
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +30 -2
- data/lib/yard/lint/validators/tags/order/parser.rb +12 -5
- data/lib/yard/lint/validators/tags/order/validator.rb +7 -2
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +21 -8
- data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +12 -5
- data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +9 -7
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +8 -2
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +1 -1
- data/lib/yard/lint/validators/warnings/invalid_directive_format/parser.rb +2 -2
- data/lib/yard/lint/validators/warnings/invalid_tag_format/parser.rb +2 -2
- data/lib/yard/lint/validators/warnings/syntax_error/config.rb +22 -0
- data/lib/yard/lint/validators/warnings/syntax_error/parser.rb +28 -0
- data/lib/yard/lint/validators/warnings/syntax_error/result.rb +27 -0
- data/lib/yard/lint/validators/warnings/syntax_error/validator.rb +15 -0
- data/lib/yard/lint/validators/warnings/syntax_error.rb +34 -0
- data/lib/yard/lint/validators/warnings/unknown_directive/parser.rb +2 -2
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/messages_builder.rb +51 -46
- data/lib/yard/lint/validators/warnings/unknown_tag/messages_builder.rb +20 -3
- data/lib/yard/lint/validators/warnings/unknown_tag/parser.rb +2 -2
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +4 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3e167ca549d10a6391cdcee97de9e53356448146f58d045dbd7aed87466460c
|
|
4
|
+
data.tar.gz: 17197fe60701c4b0f93deefb4b9b933e7deba9eaa9499d201908649b09da1019
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f274aa4a55a41ab329ba44a6c76d4d2c0c9a431ea8da8a2ded67ee2208adec3cf7a658d275fc5ef843511ddb23dfdae96575dee565cd7435b93330af614ef4b0
|
|
7
|
+
data.tar.gz: ad921b1b3156088cbaeaf760773bf1d6f947759d7b29555cb2133586d5ccf12f1b41f98968598bf3ed21e6b98755727b835088f05d760d131bc081e9adffea8a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,72 @@
|
|
|
1
|
-
|
|
1
|
+
## 1.7.0 (2026-06-15)
|
|
2
|
+
- **[Feature]** `Tags/ExampleSyntax` gained an opt-in `SkipNonRuby` option (default `false`). The validator compiles every `@example` body as Ruby, so an irb/pry session, its `=>` output, or a shell `$` transcript was reported as a syntax error. With `SkipNonRuby: true`, `@example` blocks that are interactive console transcripts are skipped. Off by default so a genuine syntax error in a normal example is not hidden.
|
|
3
|
+
- **[Feature]** `Tags/InvalidTypes` gained an opt-in `StrictConstantNames` option (default `false`). The type check was deliberately lenient - any syntactically valid constant name was accepted - so a misspelled class name like `Strng` was never flagged, defeating the validator's headline use case. With `StrictConstantNames: true`, a CamelCase type that is neither a loaded Ruby constant nor resolvable in the analyzed codebase's YARD registry is reported. It stays off by default (types defined only in un-analyzed dependencies would otherwise be flagged - add those to `ExtraTypes`); the strict template (`--init --strict`) enables it. `Boolean` is now always accepted as a pseudo-type.
|
|
4
|
+
- **[Feature]** `Tags/InformalNotation` gained an opt-in `SkipIndentedCodeBlocks` option (default `false`). The validator skipped fenced (` ``` `) code blocks but not 4-space/tab indented Markdown code blocks, so informal-looking text inside an indented code sample (e.g. `Note:`) was flagged. With `SkipIndentedCodeBlocks: true`, indented code lines are skipped too. Off by default because indented text is also used for list continuations and wrapped prose.
|
|
5
|
+
- **[Feature]** Added `Warnings/SyntaxError` (enabled by default, severity `error`). A Ruby file YARD cannot parse used to be silently skipped - it registered no objects, produced no offense, and a run could exit 0 over code that does not even parse. The parser error is now surfaced as an offense (with file and line), so the run exits non-zero. Disable with `Warnings/SyntaxError: { Enabled: false }`.
|
|
6
|
+
- **[Fix]** `Documentation/UndocumentedMethodArguments` now matches each parameter to a `@param` tag **by name** instead of only comparing counts, so a misnamed tag (e.g. `@param wrong` for `def push(item)`) is now caught - the count-only check accepted it. This is the new default; set `CheckParameterNames: false` to restore the lenient count-only comparison. A separate opt-in `SkipFullyUndocumented` (default `false`) skips methods with no documentation at all, leaving them to `Documentation/UndocumentedObjects` instead of reporting the same method twice.
|
|
7
|
+
- **[Fix]** `yard-lint --diff PATH` (e.g. `--diff lib/`) no longer fails with `fatal: ambiguous argument 'lib/...HEAD'`. Because `--diff [REF]` takes an optional argument, OptionParser consumed the path as the REF, leaving no path. The `--diff` argument is now treated as the path (with the base ref auto-detected) when it is not a resolvable git ref but is an existing path; an explicit ref (`--diff main`), ref-plus-path (`--diff main lib/`), and an unknown ref (which still errors) are unaffected.
|
|
8
|
+
- **[Fix]** `Documentation/TextSubstitution` no longer flags a forbidden string that appears inside an inline code span (`` `…` ``). It already skipped fenced ` ``` ` code blocks but matched against the raw line, so e.g. an em-dash inside `` `start — finish` `` was reported even though it is literal code, not prose to substitute. Matching now ignores inline code spans; a forbidden string elsewhere in prose on the same line is still flagged.
|
|
9
|
+
- **[Fix]** `Documentation/OrphanedDocComment` no longer treats `#`-leading lines inside heredocs or string literals as documentation comments. A line like `# @param x [Integer] …` sitting inside a SQL heredoc or a multi-line string was scanned as a real comment and reported as orphaned. The scanner now identifies comments from the Ruby lexer (`Ripper`) rather than by line prefix, so only genuine full-line comments are considered (with a regex fallback for unparseable sources). Behaviour for real comments is unchanged, pinned by an expanded characterization test suite.
|
|
10
|
+
- **[Fix]** `Documentation/OrphanedDocComment` no longer reports false positives for three more DSL constructs that YARD does document: a wrapped def (`memoize def value`, `module_function def …`), a receiver DSL call (`MyDSL.register :name do … end`), and a call whose comment carries a `@method`/`@attribute` tag naming the created object (e.g. `# @method dynamic_size` above `acts_as_counter do`). Genuinely orphaned tagged comments are still flagged.
|
|
11
|
+
- **[Fix]** `Tags/TagTypePosition` no longer misfires in two cases: a comment detached from a definition by a blank line is no longer scanned (YARD does not attach it), and under `EnforcedStyle: type_first` a valid `@option` (whose grammar is always `name [Type] :key`) is no longer flagged with the nonsensical suggestion to put the type first.
|
|
12
|
+
- **[Fix]** `Documentation/MarkdownSyntax` no longer reports the exponent operator as unclosed bold. A `**` run padded by whitespace on both sides (e.g. `x ** y`) cannot open or close CommonMark emphasis, so it is no longer counted toward the bold-marker balance. The check also now skips fenced code blocks (` ```...``` `), so a double-splat such as `def call(**opts)` inside an `@example` block is not mistaken for markdown. Genuine `**bold**`/`**unclosed` markers are still detected.
|
|
13
|
+
- **[Fix]** `--update` and `--auto-gen-config` now honor the `-c CONFIG` path. `--update -c custom.yml` updated (or errored on) `./.yard-lint.yml` instead of the named file, and `--auto-gen-config -c custom.yml` generated the todo from the custom config but linked `inherit_from` into a separate `./.yard-lint.yml` (leaving the custom config unconnected and shadowing it on later runs). Both now target the `-c` file.
|
|
14
|
+
- **[Fix]** Diff modes (`--diff`/`--staged`/`--changed`) now find changed files whose names contain non-ASCII characters. With git's default `core.quotepath=true`, such paths are emitted C-quoted (e.g. `"caf0351.rb"`), so the `.rb` suffix check failed and the file was silently skipped. git-quoted paths are now unquoted before filtering.
|
|
15
|
+
- **[Fix]** `Documentation/OrphanedDocComment` no longer treats a prose comment that merely begins with a magic-comment word (e.g. `# encoding: UTF-8 is assumed for all inputs`) as a real magic comment. It matched the prefix followed by anything, so such a line split the documentation block and the tagged part looked orphaned even though a definition followed. A magic comment is now required to have a single-token value.
|
|
16
|
+
- **[Fix]** `--changed` (uncommitted files) now includes untracked files. It used `git diff --name-only HEAD`, which lists only tracked changes, so a brand-new not-yet-staged `.rb` file was never linted despite being a working-directory change. Untracked, non-ignored files (`git ls-files --others --exclude-standard`) are now included.
|
|
17
|
+
- **[Fix]** An unnamed `@example` no longer corrupts `Tags/ExampleSyntax` / `Tags/ExampleStyle` output. YARD returns an empty string (not `nil`) for an unnamed example, so the `example.name || "Example N"` fallback never fired; the empty name line was then dropped by the parser, shifting fields - the syntax-error message ended up in the example-name slot and the actual error was lost. The fallback now triggers on an empty name.
|
|
18
|
+
- **[Fix]** Custom tags declared in a project's `.yardopts` (e.g. `--tag custom_tag:"Custom"`) are no longer reported as `Warnings/UnknownTag`. The in-process parser called `YARD.parse` directly and never loaded `.yardopts`, so a project's own tags - which the real `yard` command honors - were flagged as unknown. `.yardopts` tag-defining options (`--tag` and its `--type-tag`/`--name-tag`/etc. variants) are now registered before parsing.
|
|
19
|
+
- **[Fix]** `Tags/InformalNotation` now suggests `@note` (not `@deprecated`) for a `Warning:` notation. A warning is a caveat, not a deprecation; mapping it to `@deprecated` was incorrect. The code default and both shipped templates are updated consistently.
|
|
20
|
+
- **[Fix]** `--auto-gen-config` no longer writes offenses that are already silenced by a per-validator `Exclude` pattern into the generated baseline. `TodoGenerator#run_linting` built validator results directly, bypassing `Runner#filter_result_offenses`, so excluded files were counted and added to `.yard-lint-todo.yml`. The same exclude filtering (and no-location-offense dropping) a normal run applies is now used.
|
|
21
|
+
- **[Fix]** Documentation coverage is no longer reported as 100% when the `yard` stats subprocess fails. `StatsCalculator` returned an empty string on failure, which became the default stats (`coverage: 100.0`), so a `MinCoverage` gate silently passed; a subprocess killed by a signal also raised a `NoMethodError` on `nil.zero?`. Failure now yields unknown coverage (`nil`), and the exit-code check fails safe when a minimum is required but coverage cannot be determined.
|
|
22
|
+
- **[Fix]** Offenses with a `never` severity no longer cause a non-zero exit code under `FailOnSeverity: convention`. The convention branch counted *all* offenses via `offenses.any?`, while `#statistics` (and the error/warning branches) correctly exclude `never`. A validator set to `Severity: never` - meant to report without ever failing the build - now behaves consistently.
|
|
23
|
+
- **[Fix]** `--auto-gen-config` now emits a usable exclude pattern for grouped root-level files. `PathGrouper` produced `./**/*` for files at the project root (`File.dirname` is `.`), but `./**/*` matches nothing under `File.fnmatch` with `FNM_PATHNAME` - so the generated `.yard-lint-todo.yml` failed to exclude exactly the files it grouped, and yard-lint kept reporting them right after generating the baseline. The root group now uses `**/*`.
|
|
24
|
+
- **[Fix]** `Tags/RedundantParamDescription`'s `ParamToVerb` pattern now only fires when the word after "to" is actually a low-value verb (from the `LowValueVerbs` config). It previously flagged any `<param> to <anything>`, so meaningful noun phrases like `@param path [String] path to file` were reported as too generic.
|
|
25
|
+
- **[Fix]** `Tags/NonAsciiType` no longer flags non-ASCII characters inside string or quoted-symbol literal types (e.g. `["naïve", "plain"]`). Those are literal *values*, not Ruby type names, so non-ASCII is legitimate - and the sibling `Tags/TypeSyntax` already exempts them. Real type names with non-ASCII characters are still flagged.
|
|
26
|
+
- **[Fix]** The `Warnings/UnknownParameterName` "did you mean" suggestion engine now reads parameters from the right method and parses modern signatures. It scanned from 15 lines *before* the reported location and returned the first `def` found (so it read an earlier method's parameters), missed receiver methods like `def self.build(...)`, and mangled keyword defaults and defaults containing commas (`def f(mode: :fast, list = [1, 2], name)` parsed as `["mode fast", "list", "2]", "name"]`). It now starts at the reported def line, matches receiver/operator method names, and splits parameters with bracket awareness. The dead `yard list` fallback (which always returned `[]`, shelled out per offense, and created a `.yardoc/` directory) was removed.
|
|
27
|
+
- **[Fix]** The "did you mean" Levenshtein fallback in `Warnings/UnknownTag` and `Warnings/UnknownParameterName` no longer offers absurd suggestions for short, very different names (e.g. `@foo` → `@todo`, `@spec` → `@see`). It accepted any candidate within half the longer length; it now requires the edit distance to be strictly less than half, so a name differing by half its characters is not "corrected".
|
|
28
|
+
- **[Fix]** `AllValidators.DiffMode.DefaultBaseRef` is now honored. `--diff` with no explicit REF went straight to main/master auto-detection, ignoring the configured default (and erroring with "Could not detect default branch" on repos without main/master - exactly when the option is needed). The configured `DefaultBaseRef` is now used before falling back to auto-detection; an explicit REF still wins.
|
|
29
|
+
- **[Fix]** `Semantic/AbstractMethods` now honors its `AllowedImplementations` config option, which was defined in the defaults (and asserted by a test) but never read - the validator used a hardcoded heuristic, so customizing the option did nothing. Body lines are now checked against the configured patterns (compiled as regexps); a line matching any of them does not count as a real implementation.
|
|
30
|
+
- **[Fix]** `Tags/CollectionType` now produces a valid long-syntax suggestion for nested short-style hashes. The non-greedy `gsub(/Hash<(.+?)>/)` could not handle nesting, so `Hash<Symbol, Hash<String, Integer>>` was suggested as `Hash{Symbol => Hash<String, Integer}>` (mismatched brackets). Conversion now splits on the top-level comma with balanced-bracket awareness and recurses, yielding `Hash{Symbol => Hash{String => Integer}}`.
|
|
31
|
+
- **[Fix]** An unknown `--format` value is now rejected up front instead of after the entire (potentially long) lint run completes. `yard-lint --format xml lib/` previously analyzed everything and only then printed "Unknown format".
|
|
32
|
+
- **[Fix]** The config error for an unrecognizable validator name no longer points to a nonexistent `yard-lint --list-validators` flag (following that advice produced an invalid-option error). It now links the wiki Validators page.
|
|
33
|
+
- **[Fix]** Category-level configuration is now honored. A department key such as `Documentation:` with `Enabled: false` passed validation but was silently ignored, so the category's validators still ran. `Config#validator_enabled?` now consults a category-level `Enabled` setting (an explicit per-validator setting still takes precedence).
|
|
34
|
+
- **[Fix]** `Tags/ApiTags` no longer requires an `@api` tag on constants. The missing-tag check flagged any public non-root object, so a constant (e.g. `MAX_RETRIES = 5`) was reported as "missing @api tag" - although the validator's documentation promises it checks classes, modules, and methods. The check is now restricted to those three object types.
|
|
35
|
+
- **[Fix]** Diff modes (`--diff`, `--staged`, `--changed`) now find changed files when yard-lint is run from a repository subdirectory. `git diff --name-only` reports paths relative to the repo root, but the filter expanded them against the current working directory - so from a subdirectory every path failed `File.exist?` and the diff modes silently linted nothing (exiting 0). Paths are now expanded against the repository root (`git rev-parse --show-toplevel`).
|
|
36
|
+
- **[Fix]** Diamond-shaped config inheritance no longer raises a false `CircularDependencyError`. The loader tracked every file ever loaded instead of the files on the inheritance path currently being resolved, so two configs both inheriting one shared base (e.g. `inherit_from: [a.yml, b.yml]` where both inherit `common.yml`) aborted although no cycle exists. The tracker now acts as a proper recursion stack; true cycles are still detected.
|
|
37
|
+
- **[Fix]** `Tags/ExampleSyntax` no longer reports bogus syntax errors for example code containing a `# =>` sequence inside a string literal. The cleaner that strips YARD output markers used `line.sub(/\s*#\s*=>.*$/, '')`, truncating at the first `# =>` anywhere on the line - so `msg = "result # => not output"` became an unterminated string and was flagged as a syntax error. Stripping is now comment-aware: a `#` inside a string or character literal is left untouched.
|
|
38
|
+
- **[Fix]** `Tags/CollectionType` no longer flags custom classes whose names merely contain `Hash` or `Array` (e.g. `MyHash<String, Integer>`, `ByteArray<Integer>`). The style-detection regexes used unanchored substring matches (`/Hash<.*>/`, `/Array<.*>/`), so a custom collection type was misdetected and reported with a nonsense suggestion like `MyHash{String => Integer}`. The prefixes are now anchored with a negative lookbehind so only the built-in `Hash` and `Array` types match.
|
|
39
|
+
- **[Fix]** `Tags/Order` no longer crashes the entire run when `EnforcedOrder` is explicitly set to null in the config. The validator read the config value directly with no default fallback, so `EnforcedOrder: ~` replaced the seeded array with `nil` and `nil.dup` raised a `NoMethodError` that aborted the run. It now falls back to the default order.
|
|
40
|
+
- **[Fix]** `Tags/OptionTags` no longer demands `@option` tags for a parameter that merely has an options-like name but is documented as a non-Hash type. The check looked only at the parameter name, so a boolean keyword argument (`def run(options: false)` with `@param options [Boolean]`) or an array parameter (`@param opts [Array<String>]`) was wrongly required to document `@option`. A parameter documented with a concrete non-Hash `@param` type is now treated as not an options hash. Genuine options hashes (documented `[Hash]` or with no type) are still checked.
|
|
41
|
+
- **[Fix]** `Tags/TagGroupSeparator` no longer treats indented `@`-leading lines inside an `@example` body (e.g. an instance variable like `@result = compute`) as YARD tag groups. Real YARD tags begin at column 0 of the docstring, but the scanner stripped each line before checking for a leading `@`, so example/code content created phantom one-tag groups and spurious "missing a blank line between groups" offenses. The check now requires the `@` at column 0 of the unstripped line, matching how `Tags/Order` scans tags.
|
|
42
|
+
- **[Fix]** `Documentation/MarkdownSyntax` no longer reports a spurious "unclosed backtick" for a backtick inside a fenced code block. It counted every backtick in the docstring - including the ``` fences and code content - so a fenced block containing a lone backtick made the total odd. Inline backticks are now counted only outside fenced code blocks.
|
|
43
|
+
- **[Fix]** The in-process registry now restores YARD's global logger level even when parsing raises. The level was raised to fatal-only during parsing and reset as the final statement with no `ensure`, so an exception during `YARD.parse` left the logger silenced for the rest of the run.
|
|
44
|
+
- **[Fix]** `Documentation/UndocumentedOptions` no longer demands `@option` tags for a named parameter that merely matches an options-like name (`option`/`opts`/`options`/`kwargs`) but is documented as a non-Hash type (e.g. `@param option [Symbol]`). It looked only at the parameter name. A param documented with a concrete non-Hash `@param` type is now treated as not an options hash; double-splat (`**opts`) collectors and genuine `Hash` params are still checked.
|
|
45
|
+
- **[Fix]** `--auto-gen-config` no longer destroys the comments and formatting in an existing `.yard-lint.yml` when adding the `inherit_from` line. It round-tripped the whole config through `YAML.load_file(...).to_yaml`, deleting every comment and reformatting the file. It now edits the file textually: prepending an `inherit_from` block when none exists (preserving the rest verbatim), or inserting the todo entry into an existing block list.
|
|
46
|
+
- **[Fix]** `yard-lint --update` no longer silently drops `inherit_from` / `inherit_gem`. The updater rebuilt the config from `AllValidators` plus validator keys only and never copied or wrote the inheritance directives, so updating a config that inherits `.yard-lint-todo.yml` deleted that line and resurrected the entire baseline (every silenced offense reappeared). The directives are now preserved and written at the top of the regenerated config.
|
|
47
|
+
- **[Fix]** The one-line YARD-warning parsers (`Warnings/UnknownTag`, `Warnings/UnknownDirective`, `Warnings/InvalidTagFormat`, `Warnings/InvalidDirectiveFormat`) now extract the line number and message correctly when the source file path contains `line ` or ` in file `. The line regex `/line (\d*)/` matched the first `line ` anywhere (so a path like `.../command line tools/x.rb` yielded line 0), and the greedy message regex `/(.*) in file/` matched up to the last ` in file ` (leaking a path fragment into the message). The line regex now anchors on `near line (\d+)` and the message capture is non-greedy.
|
|
48
|
+
- **[Fix]** `Documentation/UndocumentedMethodArguments` no longer demands `@param` tags for block (`&block`) or splat (`*args`, `**opts`) parameters. The check compared the `@param` tag count against `object.parameters.size`, which counted blocks (documented with `@yield`, never `@param`) and splats - unlike every other arity computation in the gem, which excludes `*` and `&`. A method documenting its block with `@yield` (e.g. `def each(limit, &block)` with `@param limit` + `@yield`) was wrongly flagged as missing argument documentation. The count now excludes splat and block parameters, matching the gem-wide convention.
|
|
49
|
+
- **[Fix]** `Documentation/EmptyCommentLine` no longer attributes a file-header comment (separated from a definition by a blank line) to that definition. The upward scan skipped any number of blank lines before the comment block, so a header such as `# frozen_string_literal: true` + `#` was treated as a class's documentation and its bare `#` reported as an empty trailing line - although YARD only attaches a docstring that sits immediately above the definition. The scan now stops at a blank line above the definition.
|
|
50
|
+
- **[Fix]** `Documentation/BlankLineBeforeDefinition` no longer treats shebangs (`#!...`), tool sigils/directives (Sorbet `# typed:`, `# rubocop:...`, `# standard:...`), or a bare `#` comment as documentation. The upward scan stopped at the first non-magic comment and called it a doc block, so a blank line between such a non-doc comment and a definition produced a spurious "blank line between documentation and definition" offense for an undocumented class (and its suggested fix - removing the blank - would have turned the directive into the docstring). These lines are now skipped like magic comments.
|
|
51
|
+
- **[Fix]** The CLI now reports a clean error instead of a raw Ruby backtrace for an unknown command-line flag and for a missing `-c` config file. `OptionParser::ParseError` escaped the unrescued `.parse!` (so `yard-lint --bogus` dumped `OptionParser::InvalidOption` with a backtrace), and `ConfigFileNotFoundError` escaped the config-load rescue, which only handled `InvalidConfigError` (so `yard-lint -c missing.yml` dumped a backtrace). Both now print a one-line error and exit 1.
|
|
52
|
+
- **[Fix]** `Semantic/AbstractMethods` no longer flags an `@abstract` method whose body is a multi-line `raise NotImplementedError, "message"`. The body heuristic inspected each stripped source line independently, so the continuation line holding the message string looked like a real implementation (unless the message happened to contain the word `raise`). Statement continuations (a line ending in a comma or backslash) are now merged before the check.
|
|
53
|
+
- **[Fix]** `Tags/RedundantParamDescription` no longer treats any word that merely starts with "a", "an", or "the" as an article. The article regex was an unanchored prefix match, so meaningful descriptions like `@param user [User] authenticated user` or `@param id [Integer] auto-generated id` were flagged as "just restates the parameter name" (the bug also infected the `PossessiveParam` and `ArticleParamPhrase` patterns, which reuse the same regex). Articles now match whole words only; genuinely redundant descriptions like `the name` for `name` are still flagged.
|
|
54
|
+
- **[Fix]** `Tags/ApiTags` now validates only the `@api` value itself, not any indented continuation/description lines. YARD tag text includes continuation lines, so `@api private` followed by a description (e.g. `# for internal use only`) produced the value `"private\ninternal use only"`, which failed the allowed-value check (a false "invalid @api value" offense) and - because the emitted value contained a newline - corrupted the parser's two-line pairing, silently dropping later offenses. Only the first whitespace-delimited token is now validated.
|
|
55
|
+
- **[Fix]** `Semantic/AbstractMethods` and `Tags/OptionTags` offenses now include the `validator` field (the full config key, e.g. `"Semantic/AbstractMethods"`). Both validators override `Results::Base#build_offenses` and omitted the `validator: validator_name` merge the base class performs, so the text and quickfix formatters printed an empty validator path for their offenses, and anything keying on `offense[:validator]` mis-handled them.
|
|
56
|
+
- **[Fix]** `Warnings/UnknownTag` now renders a "did you mean" suggestion that resolves to a YARD directive with the `@!` prefix instead of a plain `@`. The suggestion dictionary merges tags and directives, but every suggestion was rendered as `@name` - so a directive suggestion (e.g. for `@parsee`) came out as `@parse`, and following it just produced another unknown-tag offense because the valid form is the directive `@!parse`. Directive-only suggestions are now prefixed with `@!`.
|
|
57
|
+
- **[Fix]** The shipped config templates (`--init`, `--init --strict`) no longer silently narrow validation compared to running with no config file at all. Because validator-config arrays/hashes are replaced (not merged), the stale template lists disabled real checks after materializing a config: `Tags/InvalidTypes`, `Tags/TypeSyntax`, `Tags/CollectionType`, and `Tags/NonAsciiType` lost `yieldparam`/`yieldreturn`/`raise` coverage in `ValidatedTags`, `Tags/InformalNotation` lost the `IMPORTANT`/`Important` patterns, and the strict template lost `Tags/RedundantParamDescription`'s `ArticleParamPhrase` pattern. Both templates are synced with code defaults and a parity test now prevents future drift (intentional deviations are explicitly allowlisted); two in-code fallback literals were also replaced with references to their `Config.defaults`.
|
|
58
|
+
- **[Fix]** `Documentation/UndocumentedObjects`'s `ExcludedMethods` option no longer silently suppresses offenses for classes, modules, or constants. The exclusion derived a method name with `element.split(/[#.]/).last`, which for a namespace element (no `#`/`.` separator) returned the full object path - so a method pattern like `/cache/` matched and hid the undocumented-class offense for e.g. `Memcached` while still reporting its methods. `ExcludedMethods` now only applies to method elements.
|
|
59
|
+
- **[Fix]** Missing `inherit_from` targets are no longer silently ignored. If an inherited config file (most importantly the `.yard-lint-todo.yml` baseline) was renamed, deleted, or typoed, the loader skipped it without any indication, so every baselined offense reappeared with no clue why. A warning naming the missing file (and the path it resolved to) is now printed to stderr; the rest of the config still loads.
|
|
60
|
+
- **[Fix]** Config files using YAML anchors/aliases (the common RuboCop-style shared-section idiom) no longer crash with an unrescued `Psych::AliasesNotEnabled` backtrace on Psych 4+ (Ruby >= 3.1); aliases are now explicitly enabled for all config loads (`.yard-lint.yml`, inherited files, `--update`, `--auto-gen-config`). Malformed YAML now raises the gem's own `InvalidConfigError` with the file path and parser message instead of leaking a raw `Psych::SyntaxError` backtrace.
|
|
61
|
+
- **[Fix]** Docstring-content offenses now point at the offending documentation line instead of the definition line. `Documentation/TextSubstitution` and `Tags/InformalNotation` computed a per-line offset within the docstring but reported every offense at the `def`/`class` line (e.g. an em-dash on source line 6 was reported at line 10); `Documentation/MarkdownSyntax` rendered invalid list markers with a docstring-relative index presented as a source line ("at line 4" for source line 8). All three now resolve the docstring offset against `docstring.line_range`, so editor/quickfix integrations jump to the right line.
|
|
62
|
+
- **[Fix]** `Tags/MissingYield` no longer flags methods that use `yield:` as a symbol hash key or keyword-argument label (e.g. `{ yield: true }` or `call(yield: 1)`). The detection regex guarded `:yield` and `.yield` but not the label form, so such methods were told to document a block they never yield to. A negative lookahead now excludes `yield` immediately followed by a colon; real block yields (including `yield ::Const`, which has a space before the colons) are still detected.
|
|
63
|
+
- **[Fix]** One documentation problem now produces one offense. A docstring on `attr_accessor` belongs to both generated methods (reader and writer), so a single issue was reported twice: YARD warning capture (e.g. `Warnings/UnknownTag`) emitted two identical offenses, and docstring-content validators (`Documentation/LineLength`, `Documentation/MarkdownSyntax`, `Documentation/TextSubstitution`, `Tags/InformalNotation`) scanned the shared docstring once per generated method. Identical (validator, name, location, line, message) tuples are now deduplicated in the aggregate result, and content-scanning validators process each docstring location once per run.
|
|
64
|
+
- **[Fix]** `@option` tag types and descriptions are now actually validated. YARD's `OptionTag` stores the documented option on a nested pair tag (`tag.pair.types` / `tag.pair.text`) - `tag.types` and `tag.text` are `nil` on the tag itself - so four validators silently skipped every `@option` tag despite listing `option` in their checked-tags configuration: `Tags/InvalidTypes` (invalid types in `@option` passed), `Tags/CollectionType` (collection style unenforced), `Tags/ForbiddenTags` (forbidden type patterns unmatched), and `Tags/RedundantParamDescription` (redundant option descriptions unchecked). A shared `tag_data` helper now resolves the data-carrying tag, and the redundancy check uses the option key (e.g. `:mode`) as the documented name.
|
|
65
|
+
- **[Fix]** Tags written inside `@overload` blocks now count as documentation. YARD stores them on the overload's own docstring, invisible to `object.tags`/`docstring.tags`, so methods documented entirely via `@overload` produced false positives: `Documentation/MissingReturn` demanded a `@return` the overload already had, `Documentation/UndocumentedMethodArguments` did not count overload `@param` tags, and `Tags/OptionTags` did not see overload `@option` tags. `Tags/ForbiddenTags` had the inverse problem - forbidden tags hidden inside `@overload` blocks escaped detection. All four validators now traverse overload docstrings via the shared `all_typed_tags` helper.
|
|
66
|
+
- **[Fix]** `Tags/Order` and `Tags/TagGroupSeparator` parsers now pair each offense location directly with its own payload (expected tag order / missing separators). Previously they zipped two parallel arrays by index, so a location line the downstream parser could not handle shifted every following offense onto another object's payload — reporting e.g. a method with a different method's expected tag order — while silently dropping the last offense. The historical trigger (top-level method titles failing the location regex) was fixed separately; this change removes the fragile pairing itself, so any future unparseable line drops only its own offense.
|
|
67
|
+
- **[Fix]** `Tags/Order` and `Tags/TagGroupSeparator` now check class, module, and constant docstrings. Both validators called `object.is_alias?` before checking the object type; `is_alias?` only exists on method objects, and on namespace objects YARD's `method_missing` raises `NameError` (not `NoMethodError`), which the query executor's data-error rescue silently swallowed — so every class, module, and constant was skipped by these validators. The alias check is now guarded by an `object.type == :method` test. Fixing this immediately surfaced (and fixed) two real tag-order offenses in yard-lint's own documentation that the bug had been hiding.
|
|
68
|
+
- **[Fix]** Offenses on top-level (root namespace) methods and on constants are no longer silently discarded. The location regex shared by the `Documentation/UndocumentedMethodArguments` and `Tags/InvalidTypes` parsers required a `#` or `.` separator in the object title, so titles like `#my_method` (top-level method) or `MAX_RETRIES` (constant) never matched and their offenses were dropped after the validator had already detected them. `Tags/Order` was affected too via the shared parser: a dropped location line shifted the parallel expected-order list, so surviving offenses could be reported with another method's expected tag order. The parsers now accept any object title and split namespace/method name on the last separator when one is present.
|
|
69
|
+
- **[Fix]** Boolean validator options explicitly set to `false` in `.yard-lint.yml` are no longer silently ignored. The shared config fallback (`Validators::Base#config_or_default` and `Config#get_validator_config_with_default`) used `value || default`, so a user-configured `false` fell through to the truthy default — e.g. `Tags/InformalNotation: RequireStartOfLine: false` had no effect and mid-line informal notation was never reported. The fallback now only applies when the key is genuinely unset (`nil`).
|
|
2
70
|
|
|
3
71
|
## 1.6.1 (2026-06-11)
|
|
4
72
|
- **[Fix]** `Documentation/OrphanedDocComment` no longer reports false positives for documented DSL-style method calls (e.g. `ransacker :foo do … end`, `validates :name`, `scope :active, -> { … }`). YARD's DSL handler turns such a call into a documentable method object when the preceding comment carries an implicit-docstring tag (`@return`, `@overload`, `@method`, `@attribute`, `@scope`, `@visibility`), so the comment is attached rather than dropped. The validator now recognises this case: a tagged comment before a DSL call is only flagged when the call is one YARD's handler ignores (`include`, `extend`, `private :sym`, etc.) or the comment lacks an implicit-docstring tag (in which case YARD really does drop it). Also fixes a pre-existing false positive for the bare `attr :name` attribute form.
|
data/README.md
CHANGED
|
@@ -19,10 +19,11 @@ 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; orphaned doc comments with YARD tags that YARD silently drops
|
|
23
|
-
- **Type Accuracy** - Invalid type definitions, malformed type syntax, non-ASCII characters in types, tuple types,
|
|
22
|
+
- **Documentation Completeness** - Undocumented classes, modules, methods, parameters (matched to `@param` tags by name, so a misnamed `@param` is caught), boolean return values, and missing `@return` tags; orphaned doc comments with YARD tags that YARD silently drops
|
|
23
|
+
- **Type Accuracy** - Invalid type definitions, malformed type syntax, non-ASCII characters in types, tuple types, literal types (symbols, strings, numbers), and misspelled class names (opt-in via `StrictConstantNames`)
|
|
24
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
|
+
- **Parse Errors** - Files YARD cannot parse (syntax errors) are reported instead of silently skipped, so a run never passes over code that does not parse
|
|
26
|
+
- **Code Examples** - Syntax validation in `@example` tags (with an opt-in skip for irb/pry/shell console transcripts), optional style validation with RuboCop/StandardRB
|
|
26
27
|
- **Semantic Correctness** - Abstract methods with implementations, redundant descriptions
|
|
27
28
|
- **Style & Formatting** - Empty comment lines, blank lines before definitions, informal notation patterns, tag group separators, configurable documentation line length (opt-in)
|
|
28
29
|
- **Smart Suggestions** - "Did you mean" suggestions for typos in parameter names, tags, and configuration settings
|
data/bin/yard-lint
CHANGED
|
@@ -10,7 +10,8 @@ options = {}
|
|
|
10
10
|
config_file = nil
|
|
11
11
|
diff_mode = nil
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
begin
|
|
14
|
+
OptionParser.new do |opts|
|
|
14
15
|
opts.banner = 'Usage: yard-lint [options] [PATH]'
|
|
15
16
|
|
|
16
17
|
opts.on('-c', '--config FILE', 'Path to config file (default: .yard-lint.yml)') do |file|
|
|
@@ -125,7 +126,25 @@ OptionParser.new do |opts|
|
|
|
125
126
|
puts ' cat lib/foo.rb | yard-lint --stdin lib/foo.rb # Lint source piped from stdin'
|
|
126
127
|
exit
|
|
127
128
|
end
|
|
128
|
-
end.parse!
|
|
129
|
+
end.parse!
|
|
130
|
+
rescue OptionParser::ParseError => e
|
|
131
|
+
warn "Error: #{e.message}"
|
|
132
|
+
warn 'Run `yard-lint --help` for usage.'
|
|
133
|
+
exit 1
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Disambiguate `--diff PATH`. `--diff [REF]` takes an optional argument, so
|
|
137
|
+
# `yard-lint --diff lib/` makes OptionParser consume `lib/` as the REF, leaving
|
|
138
|
+
# no PATH (the run then tries `git diff lib/...HEAD` and fails). When the --diff
|
|
139
|
+
# argument is not a resolvable git ref but is an existing path and no explicit
|
|
140
|
+
# path was given, treat it as the PATH and let the base ref auto-detect.
|
|
141
|
+
if diff_mode && diff_mode[:mode] == :ref && diff_mode[:base_ref] && ARGV.empty?
|
|
142
|
+
candidate = diff_mode[:base_ref]
|
|
143
|
+
if File.exist?(candidate) && !Yard::Lint::Git.branch_exists?(candidate)
|
|
144
|
+
ARGV.unshift(candidate)
|
|
145
|
+
diff_mode[:base_ref] = nil
|
|
146
|
+
end
|
|
147
|
+
end
|
|
129
148
|
|
|
130
149
|
# Handle --init flag
|
|
131
150
|
if options[:init]
|
|
@@ -146,7 +165,7 @@ end
|
|
|
146
165
|
# Handle --update flag
|
|
147
166
|
if options[:update]
|
|
148
167
|
begin
|
|
149
|
-
result = Yard::Lint::ConfigUpdater.update(strict: options[:strict])
|
|
168
|
+
result = Yard::Lint::ConfigUpdater.update(path: config_file, strict: options[:strict])
|
|
150
169
|
|
|
151
170
|
if result[:added].any? || result[:removed].any?
|
|
152
171
|
puts 'Updated .yard-lint.yml:'
|
|
@@ -185,7 +204,8 @@ if options[:auto_gen_config]
|
|
|
185
204
|
path: path,
|
|
186
205
|
config: config,
|
|
187
206
|
force: options[:force] || false,
|
|
188
|
-
exclude_limit: options[:exclude_limit] || 15
|
|
207
|
+
exclude_limit: options[:exclude_limit] || 15,
|
|
208
|
+
config_path: config_file
|
|
189
209
|
)
|
|
190
210
|
|
|
191
211
|
puts result[:message]
|
|
@@ -225,6 +245,9 @@ begin
|
|
|
225
245
|
else
|
|
226
246
|
Yard::Lint::Config.load || Yard::Lint::Config.new
|
|
227
247
|
end
|
|
248
|
+
rescue Yard::Lint::Errors::ConfigFileNotFoundError => e
|
|
249
|
+
puts "Configuration Error: #{e.message}"
|
|
250
|
+
exit 1
|
|
228
251
|
rescue Yard::Lint::Errors::InvalidConfigError => e
|
|
229
252
|
puts "Configuration Error:\n#{e.message}"
|
|
230
253
|
exit 1
|
|
@@ -251,6 +274,14 @@ if options[:only]
|
|
|
251
274
|
config.only_validators = options[:only]
|
|
252
275
|
end
|
|
253
276
|
|
|
277
|
+
# Validate the output format before doing any (potentially long) work, rather
|
|
278
|
+
# than running the whole lint and only then rejecting an unknown format.
|
|
279
|
+
valid_formats = %w[text json quickfix]
|
|
280
|
+
if options[:format] && !valid_formats.include?(options[:format])
|
|
281
|
+
puts "Error: Unknown format '#{options[:format]}'. Valid formats: #{valid_formats.join(', ')}"
|
|
282
|
+
exit 1
|
|
283
|
+
end
|
|
284
|
+
|
|
254
285
|
# Suppress progress for quickfix to avoid contaminating machine-readable output
|
|
255
286
|
options[:progress] = false if options[:format] == 'quickfix' && options[:progress].nil?
|
|
256
287
|
|
data/lib/yard/lint/config.rb
CHANGED
|
@@ -155,6 +155,20 @@ module Yard
|
|
|
155
155
|
# If --only is specified, it takes full control
|
|
156
156
|
return only_validators.include?(validator_name) if only_validators.any?
|
|
157
157
|
|
|
158
|
+
# An explicit per-validator Enabled in the user's config wins.
|
|
159
|
+
raw_validator = @raw_config[validator_name]
|
|
160
|
+
if raw_validator.is_a?(Hash) && raw_validator.key?('Enabled')
|
|
161
|
+
return raw_validator['Enabled'] != false
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Otherwise honor a category-level Enabled (e.g. `Documentation:
|
|
165
|
+
# { Enabled: false }`), which previously validated but was ignored.
|
|
166
|
+
category = validator_name.split('/').first
|
|
167
|
+
raw_category = @raw_config[category]
|
|
168
|
+
if raw_category.is_a?(Hash) && raw_category.key?('Enabled')
|
|
169
|
+
return raw_category['Enabled'] != false
|
|
170
|
+
end
|
|
171
|
+
|
|
158
172
|
validator_config = validators[validator_name] || {}
|
|
159
173
|
validator_config['Enabled'] != false # Default to true
|
|
160
174
|
end
|
|
@@ -243,10 +257,13 @@ module Yard
|
|
|
243
257
|
# @param key [String] configuration key
|
|
244
258
|
# @return [Object, nil] configuration value or default
|
|
245
259
|
def get_validator_config_with_default(validator_name, key)
|
|
246
|
-
validator_config(validator_name, key)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
260
|
+
value = validator_config(validator_name, key)
|
|
261
|
+
# A nil? check (not ||) so that explicitly configured false values
|
|
262
|
+
# are honored instead of falling back to a truthy default
|
|
263
|
+
return value unless value.nil?
|
|
264
|
+
|
|
265
|
+
validator_cfg = ConfigLoader.validator_config(validator_name)
|
|
266
|
+
validator_cfg&.defaults&.dig(key)
|
|
250
267
|
end
|
|
251
268
|
|
|
252
269
|
# Get AllValidators section
|
|
@@ -79,6 +79,19 @@ module Yard
|
|
|
79
79
|
def load(path)
|
|
80
80
|
new(path).load
|
|
81
81
|
end
|
|
82
|
+
|
|
83
|
+
# Load a YAML config file with alias/anchor support and clean errors.
|
|
84
|
+
# Psych 4+ rejects aliases by default, breaking the common RuboCop-style
|
|
85
|
+
# shared-section idiom; malformed YAML would otherwise escape as a raw
|
|
86
|
+
# Psych::SyntaxError backtrace.
|
|
87
|
+
# @param path [String] path to the YAML file
|
|
88
|
+
# @return [Hash] parsed YAML, or an empty hash for empty files
|
|
89
|
+
# @raise [Errors::InvalidConfigError] when the file contains invalid YAML
|
|
90
|
+
def load_yaml_file(path)
|
|
91
|
+
YAML.load_file(path, aliases: true) || {}
|
|
92
|
+
rescue Psych::SyntaxError => e
|
|
93
|
+
raise Errors::InvalidConfigError, "Invalid YAML in #{path}: #{e.message}"
|
|
94
|
+
end
|
|
82
95
|
end
|
|
83
96
|
|
|
84
97
|
# All validator names (auto-discovered from codebase structure)
|
|
@@ -110,20 +123,27 @@ module Yard
|
|
|
110
123
|
# @return [Hash] configuration hash with inheritance resolved
|
|
111
124
|
# @raise [Yard::Lint::Errors::CircularDependencyError] if circular dependency detected
|
|
112
125
|
def load_file(path)
|
|
113
|
-
#
|
|
126
|
+
# A cycle exists only when the file is an ancestor on the inheritance
|
|
127
|
+
# path currently being loaded - @loaded_files acts as that stack.
|
|
128
|
+
# A file reachable through several branches (diamond inheritance,
|
|
129
|
+
# e.g. two configs sharing a common base) is legal.
|
|
114
130
|
if @loaded_files.include?(path)
|
|
115
131
|
raise Errors::CircularDependencyError, "Circular dependency detected: #{path}"
|
|
116
132
|
end
|
|
117
133
|
|
|
118
134
|
@loaded_files << path
|
|
119
135
|
|
|
120
|
-
|
|
136
|
+
begin
|
|
137
|
+
yaml = self.class.load_yaml_file(path)
|
|
121
138
|
|
|
122
|
-
|
|
123
|
-
|
|
139
|
+
# Handle inheritance
|
|
140
|
+
base_config = load_inherited_configs(yaml, File.dirname(path))
|
|
124
141
|
|
|
125
|
-
|
|
126
|
-
|
|
142
|
+
# Merge current config over inherited config
|
|
143
|
+
merge_configs(base_config, yaml)
|
|
144
|
+
ensure
|
|
145
|
+
@loaded_files.pop
|
|
146
|
+
end
|
|
127
147
|
end
|
|
128
148
|
|
|
129
149
|
# Load all inherited configurations
|
|
@@ -141,6 +161,11 @@ module Yard
|
|
|
141
161
|
if File.exist?(inherited_path)
|
|
142
162
|
inherited = load_file(inherited_path)
|
|
143
163
|
config = merge_configs(config, inherited)
|
|
164
|
+
else
|
|
165
|
+
# Stay loud: silently skipping makes a renamed/deleted todo
|
|
166
|
+
# baseline resurrect every baselined offense with no clue why
|
|
167
|
+
$stderr.puts "Warning: inherit_from file '#{file}' not found " \
|
|
168
|
+
"(resolved to #{inherited_path}) - skipping"
|
|
144
169
|
end
|
|
145
170
|
end
|
|
146
171
|
end
|
|
@@ -69,7 +69,7 @@ module Yard
|
|
|
69
69
|
# Load the existing user config
|
|
70
70
|
# @return [Hash] parsed YAML config
|
|
71
71
|
def load_existing_config
|
|
72
|
-
|
|
72
|
+
ConfigLoader.load_yaml_file(@path)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
# Load the template config
|
|
@@ -96,6 +96,14 @@ module Yard
|
|
|
96
96
|
# Build merged config
|
|
97
97
|
merged = {}
|
|
98
98
|
|
|
99
|
+
# Preserve inheritance directives (inherit_from / inherit_gem) so that
|
|
100
|
+
# --update does not silently drop them - otherwise a .yard-lint-todo.yml
|
|
101
|
+
# baseline (or an inherited gem config) is lost and every silenced
|
|
102
|
+
# offense reappears.
|
|
103
|
+
%w[inherit_from inherit_gem].each do |key|
|
|
104
|
+
merged[key] = existing[key] if existing.key?(key)
|
|
105
|
+
end
|
|
106
|
+
|
|
99
107
|
# Copy AllValidators from existing, falling back to template
|
|
100
108
|
merged['AllValidators'] = existing['AllValidators'] || template['AllValidators']
|
|
101
109
|
|
|
@@ -158,6 +166,19 @@ module Yard
|
|
|
158
166
|
lines << '# See https://github.com/mensfeld/yard-lint for documentation'
|
|
159
167
|
lines << ''
|
|
160
168
|
|
|
169
|
+
# Write inheritance directives first (these must be preserved across updates)
|
|
170
|
+
if config['inherit_from']
|
|
171
|
+
lines << 'inherit_from:'
|
|
172
|
+
Array(config['inherit_from']).each { |file| lines << " - #{file}" }
|
|
173
|
+
lines << ''
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if config['inherit_gem'].is_a?(Hash)
|
|
177
|
+
lines << 'inherit_gem:'
|
|
178
|
+
config['inherit_gem'].each { |gem_name, gem_file| lines << " #{gem_name}: #{gem_file}" }
|
|
179
|
+
lines << ''
|
|
180
|
+
end
|
|
181
|
+
|
|
161
182
|
# Write AllValidators section
|
|
162
183
|
if config['AllValidators']
|
|
163
184
|
lines << '# Global settings for all validators'
|
|
@@ -202,7 +202,8 @@ module Yard
|
|
|
202
202
|
if suggestions.any?
|
|
203
203
|
@errors << " Did you mean: #{suggestions.first}?"
|
|
204
204
|
else
|
|
205
|
-
@errors << '
|
|
205
|
+
@errors << ' See https://github.com/mensfeld/yard-lint/wiki/Validators ' \
|
|
206
|
+
'for all available validators'
|
|
206
207
|
end
|
|
207
208
|
end
|
|
208
209
|
|
|
@@ -30,36 +30,44 @@ module Yard
|
|
|
30
30
|
|
|
31
31
|
YARD::Registry.clear
|
|
32
32
|
|
|
33
|
+
# Register custom tags declared in .yardopts so the parser does not
|
|
34
|
+
# warn about them (matching how the real `yard` command behaves).
|
|
35
|
+
register_yardopts_tags
|
|
36
|
+
|
|
33
37
|
# Suppress YARD's default output by setting log level high
|
|
34
38
|
# YARD uses its own logging levels, 4 is ERROR/FATAL level
|
|
35
39
|
original_level = YARD::Logger.instance.level
|
|
36
40
|
YARD::Logger.instance.level = 4 # Only show fatal errors
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
begin
|
|
43
|
+
if source
|
|
44
|
+
virtual_path = files.first
|
|
45
|
+
# First pass: register directive/macro definitions from the in-memory source.
|
|
46
|
+
# We set parser.file manually so registered objects carry the virtual path.
|
|
47
|
+
parse_source_string(source, virtual_path)
|
|
48
|
+
# Clear checksums so the second pass is not skipped
|
|
49
|
+
YARD::Registry.checksums.clear
|
|
50
|
+
# Second pass: full parse with all directives available
|
|
51
|
+
@warnings = capture_warnings { parse_source_string(source, virtual_path) }
|
|
52
|
+
else
|
|
53
|
+
# First pass: parse all files to process directive definitions
|
|
54
|
+
YARD.parse(files)
|
|
55
|
+
|
|
56
|
+
# Clear checksums to force reparsing without clearing the registry.
|
|
57
|
+
# This allows macro definitions from the first pass to be available
|
|
58
|
+
# during the second pass, enabling proper directive expansion regardless of parse order.
|
|
59
|
+
YARD::Registry.checksums.clear
|
|
60
|
+
|
|
61
|
+
# Second pass: reparse files now that all directive definitions are available
|
|
62
|
+
@warnings = capture_warnings { YARD.parse(files) }
|
|
63
|
+
end
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
@parsed = true
|
|
66
|
+
ensure
|
|
67
|
+
# Restore the log level even if parsing raises, so a single bad
|
|
68
|
+
# file does not leave YARD's global logger silenced for the run.
|
|
69
|
+
YARD::Logger.instance.level = original_level
|
|
58
70
|
end
|
|
59
|
-
|
|
60
|
-
@parsed = true
|
|
61
|
-
|
|
62
|
-
YARD::Logger.instance.level = original_level
|
|
63
71
|
end
|
|
64
72
|
end
|
|
65
73
|
|
|
@@ -124,6 +132,33 @@ module Yard
|
|
|
124
132
|
|
|
125
133
|
private
|
|
126
134
|
|
|
135
|
+
# Reads .yardopts (if present in the working directory) and registers
|
|
136
|
+
# any custom tags it declares, so YARD does not emit "Unknown tag"
|
|
137
|
+
# warnings for tags the project has defined - the same tags the real
|
|
138
|
+
# `yard` command would honor.
|
|
139
|
+
# @return [void]
|
|
140
|
+
def register_yardopts_tags
|
|
141
|
+
yardopts = File.join(Dir.pwd, '.yardopts')
|
|
142
|
+
return unless File.exist?(yardopts)
|
|
143
|
+
|
|
144
|
+
require 'shellwords'
|
|
145
|
+
args = Shellwords.split(File.read(yardopts))
|
|
146
|
+
index = 0
|
|
147
|
+
while index < args.length
|
|
148
|
+
# Tag-defining options: --tag, --type-tag, --type-name-tag,
|
|
149
|
+
# --name-tag, --title-tag all make the tag name "known".
|
|
150
|
+
if args[index] =~ /\A--(?:type-|type-name-|name-|title-)?tag\z/ && args[index + 1]
|
|
151
|
+
name = args[index + 1].split(':', 2).first
|
|
152
|
+
YARD::Tags::Library.define_tag(name, name.to_sym) if name && !name.empty?
|
|
153
|
+
index += 2
|
|
154
|
+
else
|
|
155
|
+
index += 1
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
rescue StandardError => e
|
|
159
|
+
warn "[YARD::Lint] Failed to load .yardopts tags: #{e.message}" if ENV['DEBUG']
|
|
160
|
+
end
|
|
161
|
+
|
|
127
162
|
# Parse Ruby source from a string and register objects under a virtual path.
|
|
128
163
|
# YARD::Parser::SourceParser#parse accepts a StringIO but keeps @file as '(stdin)'
|
|
129
164
|
# unless we set it explicitly before parsing.
|
|
@@ -9,6 +9,7 @@ module Yard
|
|
|
9
9
|
# Patterns matching the 'general' regexps from each warning validator's parser.
|
|
10
10
|
# These patterns identify which validator should handle each warning.
|
|
11
11
|
PATTERNS = {
|
|
12
|
+
'Warnings/SyntaxError' => /^\[warn\]: Syntax error in /,
|
|
12
13
|
'Warnings/UnknownTag' => /^\[warn\]: Unknown tag.*@.*near line/,
|
|
13
14
|
'Warnings/UnknownParameterName' => /^\[warn\]: @param tag has unknown parameter name/,
|
|
14
15
|
'Warnings/DuplicatedParameterName' => /^\[warn\]: @param tag has duplicate parameter name/,
|
data/lib/yard/lint/git.rb
CHANGED
|
@@ -71,18 +71,33 @@ module Yard
|
|
|
71
71
|
def uncommitted_files(path)
|
|
72
72
|
ensure_git_repository!
|
|
73
73
|
|
|
74
|
-
# Get both staged and unstaged changes
|
|
74
|
+
# Get both staged and unstaged changes to tracked files
|
|
75
75
|
stdout, stderr, status = Open3.capture3('git', 'diff', '--name-only', 'HEAD')
|
|
76
76
|
|
|
77
77
|
unless status.success?
|
|
78
78
|
raise Error, "Git diff failed: #{stderr.strip}"
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
files = stdout.split("\n")
|
|
82
|
+
|
|
83
|
+
# Also include untracked (but not git-ignored) files - "all changes in
|
|
84
|
+
# the working directory" should cover newly added, not-yet-staged files.
|
|
85
|
+
others_stdout, _stderr, others_status =
|
|
86
|
+
Open3.capture3('git', 'ls-files', '--others', '--exclude-standard')
|
|
87
|
+
files += others_stdout.split("\n") if others_status.success?
|
|
88
|
+
|
|
89
|
+
filter_ruby_files(files.uniq, path)
|
|
82
90
|
end
|
|
83
91
|
|
|
84
92
|
private
|
|
85
93
|
|
|
94
|
+
# Absolute path to the repository root (git reports paths relative to it)
|
|
95
|
+
# @return [String] repo root, or the current directory if it can't be determined
|
|
96
|
+
def repository_root
|
|
97
|
+
stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--show-toplevel')
|
|
98
|
+
status.success? ? stdout.strip : Dir.pwd
|
|
99
|
+
end
|
|
100
|
+
|
|
86
101
|
# Ensure we're in a git repository
|
|
87
102
|
# @raise [Error] if not in a git repository
|
|
88
103
|
def ensure_git_repository!
|
|
@@ -99,14 +114,40 @@ module Yard
|
|
|
99
114
|
# @return [Array<String>] absolute paths to Ruby files that exist
|
|
100
115
|
def filter_ruby_files(files, path)
|
|
101
116
|
base_path = File.expand_path(path)
|
|
117
|
+
# git reports paths relative to the repository root, which is not
|
|
118
|
+
# necessarily the current working directory, so expand against the
|
|
119
|
+
# repo root - otherwise running from a subdirectory finds nothing.
|
|
120
|
+
root = repository_root
|
|
102
121
|
|
|
103
122
|
files
|
|
123
|
+
.map { |f| unquote_git_path(f) }
|
|
104
124
|
.select { |f| f.end_with?('.rb') }
|
|
105
|
-
.map { |f| File.expand_path(f) }
|
|
125
|
+
.map { |f| File.expand_path(f, root) }
|
|
106
126
|
.select { |f| File.exist?(f) } # Skip deleted files
|
|
107
127
|
.select { |f| file_within_path?(f, base_path) }
|
|
108
128
|
end
|
|
109
129
|
|
|
130
|
+
# Unquotes a path as git emits it with the default core.quotepath=true:
|
|
131
|
+
# paths containing non-ASCII (or special) bytes are wrapped in double
|
|
132
|
+
# quotes with C-style escapes (e.g. "caf\303\251.rb"). Without this such
|
|
133
|
+
# files end with `"` rather than `.rb` and are silently dropped.
|
|
134
|
+
# @param path [String] a path from git output
|
|
135
|
+
# @return [String] the unquoted path
|
|
136
|
+
def unquote_git_path(path)
|
|
137
|
+
return path unless path.start_with?('"') && path.end_with?('"')
|
|
138
|
+
|
|
139
|
+
inner = path[1..-2]
|
|
140
|
+
unescaped = inner.gsub(/\\(?:(\d{3})|(.))/) do
|
|
141
|
+
octal = Regexp.last_match(1)
|
|
142
|
+
if octal
|
|
143
|
+
octal.to_i(8).chr
|
|
144
|
+
else
|
|
145
|
+
{ 't' => "\t", 'n' => "\n", 'r' => "\r" }.fetch(Regexp.last_match(2), Regexp.last_match(2))
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
unescaped.force_encoding(Encoding::UTF_8)
|
|
149
|
+
end
|
|
150
|
+
|
|
110
151
|
# Check if file is within the specified path
|
|
111
152
|
# @param file [String] absolute file path
|
|
112
153
|
# @param base_path [String] absolute base path
|
|
@@ -33,7 +33,10 @@ module Yard
|
|
|
33
33
|
|
|
34
34
|
by_dir.each do |dir, dir_files|
|
|
35
35
|
if should_group_directory?(dir, dir_files.uniq, limit)
|
|
36
|
-
|
|
36
|
+
# For root-level files File.dirname is ".", and "./**/*" matches
|
|
37
|
+
# nothing under File.fnmatch (FNM_PATHNAME), so the generated todo
|
|
38
|
+
# file would fail to exclude them. Use a plain recursive glob.
|
|
39
|
+
result << (dir == '.' ? '**/*' : "#{dir}/**/*")
|
|
37
40
|
else
|
|
38
41
|
result.concat(dir_files.uniq)
|
|
39
42
|
end
|