yard-lint 1.11.0 → 1.13.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 +201 -498
- data/README.md +103 -1
- data/bin/yard-lint +100 -40
- data/lib/yard/lint/config.rb +81 -5
- data/lib/yard/lint/config_validator.rb +7 -191
- data/lib/yard/lint/contractable/contract.rb +219 -0
- data/lib/yard/lint/contractable/result.rb +93 -0
- data/lib/yard/lint/contractable/rule.rb +10 -0
- data/lib/yard/lint/contracts/base.rb +57 -0
- data/lib/yard/lint/contracts/category.rb +71 -0
- data/lib/yard/lint/contracts/config.rb +81 -0
- data/lib/yard/lint/contracts/validator.rb +72 -0
- data/lib/yard/lint/errors.rb +3 -0
- data/lib/yard/lint/executor/in_process_registry.rb +1 -1
- data/lib/yard/lint/explainer.rb +205 -0
- data/lib/yard/lint/formatters/sarif.rb +218 -0
- data/lib/yard/lint/git.rb +5 -3
- data/lib/yard/lint/result_builder.rb +7 -6
- data/lib/yard/lint/runner.rb +2 -21
- data/lib/yard/lint/templates/default_config.yml +10 -0
- data/lib/yard/lint/templates/strict_config.yml +10 -0
- data/lib/yard/lint/todo_generator.rb +143 -12
- data/lib/yard/lint/validators/base.rb +63 -0
- data/lib/yard/lint/validators/documentation/reference_typo/config.rb +20 -0
- data/lib/yard/lint/validators/documentation/reference_typo/messages_builder.rb +24 -0
- data/lib/yard/lint/validators/documentation/reference_typo/parser.rb +44 -0
- data/lib/yard/lint/validators/documentation/reference_typo/result.rb +27 -0
- data/lib/yard/lint/validators/documentation/reference_typo/validator.rb +214 -0
- data/lib/yard/lint/validators/documentation/reference_typo.rb +39 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/parser.rb +13 -12
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +84 -5
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +16 -3
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +3 -2
- data/lib/yard/lint/validators/tags/example_style/validator.rb +4 -4
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +4 -2
- data/lib/yard/lint/validators/tags/invalid_types/messages_builder.rb +15 -4
- data/lib/yard/lint/validators/tags/invalid_types/parser.rb +18 -7
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +22 -12
- data/lib/yard/lint/validators/tags/overload_consistency/config.rb +20 -0
- data/lib/yard/lint/validators/tags/overload_consistency/messages_builder.rb +32 -0
- data/lib/yard/lint/validators/tags/overload_consistency/parser.rb +49 -0
- data/lib/yard/lint/validators/tags/overload_consistency/result.rb +27 -0
- data/lib/yard/lint/validators/tags/overload_consistency/validator.rb +71 -0
- data/lib/yard/lint/validators/tags/overload_consistency.rb +53 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +3 -3
- data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +8 -0
- data/lib/yard/lint/validators/tags/tag_separator/validator.rb +8 -0
- data/lib/yard/lint/validators/tags/type_syntax/parser.rb +13 -2
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +11 -1
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +3 -57
- metadata +23 -2
data/README.md
CHANGED
|
@@ -114,8 +114,14 @@ yard-lint --auto-gen-config
|
|
|
114
114
|
yard-lint lib/
|
|
115
115
|
|
|
116
116
|
# Fix violations incrementally by removing exclusions from .yard-lint-todo.yml
|
|
117
|
+
|
|
118
|
+
# Fail (exit 1) when .yard-lint-todo.yml has entries that no longer match any offense
|
|
119
|
+
# (use the PATH of your normal run; a PATH only limits which entries are reported)
|
|
120
|
+
yard-lint --check-todo lib/
|
|
117
121
|
```
|
|
118
122
|
|
|
123
|
+
`.yard-lint-todo.yml` is written and read next to the loaded `.yard-lint.yml` (or in the current directory without one), and its entries are relative to that directory, so the baseline works from any directory you run yard-lint in.
|
|
124
|
+
|
|
119
125
|
**Learn more:** [Incremental Adoption Guide](https://github.com/mensfeld/yard-lint/wiki/Incremental-Adoption)
|
|
120
126
|
|
|
121
127
|
### Check Documentation Coverage
|
|
@@ -143,6 +149,14 @@ yard-lint lib/ --only Tags/Order,Documentation/UndocumentedObjects
|
|
|
143
149
|
|
|
144
150
|
**Learn more:** [Advanced Usage Guide](https://github.com/mensfeld/yard-lint/wiki/Advanced-Usage)
|
|
145
151
|
|
|
152
|
+
### Explain a Validator
|
|
153
|
+
|
|
154
|
+
Print what a validator checks, its default severity, configuration options, and Bad/Good examples directly in the terminal - no need to open the wiki. The explanation is sourced from the validator's own YARD documentation.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
yard-lint --explain Tags/TypeSyntax
|
|
158
|
+
```
|
|
159
|
+
|
|
146
160
|
### Lint from stdin (LSP / Editor Integration)
|
|
147
161
|
|
|
148
162
|
Pass source bytes directly without reading from disk. The `path` argument is still required - it governs config resolution, exclusion matching, and offense location reporting.
|
|
@@ -200,6 +214,26 @@ Then `:make` lints the current file and `:cnext` / `:cprev` jump between offense
|
|
|
200
214
|
|
|
201
215
|
Then `M-x compile` and `M-g n` / `M-g p` navigate between offenses.
|
|
202
216
|
|
|
217
|
+
### SARIF Output (GitHub Code Scanning)
|
|
218
|
+
|
|
219
|
+
Use `--format sarif` to emit [SARIF 2.1.0](https://sarifweb.azurewebsites.net/), the format GitHub code scanning and most result aggregators ingest. Uploading it makes yard-lint offenses appear as annotations in the PR "Files changed" view and the repository Security tab, instead of being buried in CI logs:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
yard-lint --format sarif lib/ > yard-lint.sarif
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Each rule carries its description (from the validator's own YARD docs), and severities map to SARIF levels (`error` → error, `warning` → warning, `convention` → note). Artifact paths are emitted repository-relative so GitHub maps them onto your files. In GitHub Actions, upload the file with `github/codeql-action/upload-sarif`:
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
- run: bundle exec yard-lint --format sarif lib/ > yard-lint.sarif
|
|
229
|
+
continue-on-error: true
|
|
230
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
231
|
+
with:
|
|
232
|
+
sarif_file: yard-lint.sarif
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
(Run yard-lint from the repository root so paths resolve correctly; `continue-on-error` lets the upload step run even when offenses are found.)
|
|
236
|
+
|
|
203
237
|
## Configuration Basics
|
|
204
238
|
|
|
205
239
|
Create a `.yard-lint.yml` file in your project root:
|
|
@@ -314,10 +348,35 @@ Documentation/UnderfilledLines:
|
|
|
314
348
|
|
|
315
349
|
**Key features:**
|
|
316
350
|
- Per-validator control (enable/disable, severity, exclusions)
|
|
351
|
+
- Category-level defaults (enable/disable, severity, exclusions) for a whole department
|
|
317
352
|
- Configuration inheritance with `inherit_from` and `inherit_gem`
|
|
318
353
|
- Automatic configuration validation with helpful error messages
|
|
319
354
|
- Per-validator YARD options and file exclusions
|
|
320
355
|
|
|
356
|
+
**Category-level defaults:** set `Enabled` or `Severity` on a category (`Documentation`, `Tags`, `Warnings`, `Semantic`) to apply it to every validator in that department. A per-validator setting always wins over the category, which in turn wins over the validator's built-in default:
|
|
357
|
+
|
|
358
|
+
```yaml
|
|
359
|
+
# Make every Tags/* offense an error...
|
|
360
|
+
Tags:
|
|
361
|
+
Severity: error
|
|
362
|
+
|
|
363
|
+
# ...except Tags/Order, which stays a convention
|
|
364
|
+
Tags/Order:
|
|
365
|
+
Severity: convention
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
A category-level `Exclude` is combined with each validator's own `Exclude` (it is not replaced by it), so these files are skipped by every `Documentation/*` validator, including one that sets its own `Exclude`:
|
|
369
|
+
|
|
370
|
+
```yaml
|
|
371
|
+
Documentation:
|
|
372
|
+
Exclude:
|
|
373
|
+
- 'lib/generated/**/*'
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
A category accepts only the keys `Enabled` (`true` or `false`), `Severity` and `Exclude`; an unknown key or a non-boolean `Enabled` fails configuration validation.
|
|
377
|
+
|
|
378
|
+
`Exclude` patterns at every level are relative to the directory of the loaded `.yard-lint.yml` (or the current directory without one), whatever PATH or directory you run yard-lint from. A file outside that directory is matched as `../...` (with `--config config/yard-lint.yml`, write `../lib/generated/**/*`); absolute patterns also work.
|
|
379
|
+
|
|
321
380
|
**Learn more:** [Complete Configuration Guide](https://github.com/mensfeld/yard-lint/wiki/Configuration)
|
|
322
381
|
|
|
323
382
|
## Catching Orphaned Documentation Comments
|
|
@@ -426,6 +485,47 @@ The validator reports one offense per wasteful paragraph and is deliberately con
|
|
|
426
485
|
|
|
427
486
|
Unlike `LineLength` (which measures an objective property), "this line should have been longer" is a stylistic judgement. Projects that use **semantic line breaks** (one sentence or clause per line, see [sembr.org](https://sembr.org)) are never flagged - any paragraph whose non-final lines all end at a sentence boundary (`.?!:;`) is treated as intentional. Leave the validator off, or add `,` to `SentenceEndChars`, if that style is used heavily.
|
|
428
487
|
|
|
488
|
+
## Catching Typos in References
|
|
489
|
+
|
|
490
|
+
The `Documentation/ReferenceTypo` validator (enabled by default, severity `warning`) checks `{Foo#bar}` links in docstrings and tag text, and `@see` targets. When a reference does not resolve and a close match exists in its namespace, it reports the reference and suggests the closest name:
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
# Bad - reports: Reference 'Parser#prase' does not resolve (did you mean 'Parser#parse'?)
|
|
494
|
+
# Delegates to {Parser#prase}
|
|
495
|
+
def call; end
|
|
496
|
+
|
|
497
|
+
# Good
|
|
498
|
+
# Delegates to {Parser#parse}
|
|
499
|
+
def call; end
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
To keep false positives low, it skips references into namespaces YARD does not know (stdlib, other gems), methods that a loaded Ruby ancestor may define (e.g. `{#to_s}`), names with no close match, and code spans and code blocks. A bare constant such as `{Kafka}` is only compared with constants of the enclosing namespaces, never with top-level ones, so a constant of a gem YARD did not parse is not reported as a typo of a similar project name.
|
|
503
|
+
|
|
504
|
+
## Checking @overload Tags
|
|
505
|
+
|
|
506
|
+
YARD checks `@param` names only against the method's own signature, never the tags inside an `@overload`. The `Tags/OverloadConsistency` validator (enabled by default, severity `warning`) checks each overload against its own signature and reports:
|
|
507
|
+
|
|
508
|
+
- a `@param` that names no parameter of that overload
|
|
509
|
+
- a parameter with no `@param`, when the overload documents at least one other parameter (splat and block parameters are not required; a `@param` on the method docstring counts for every overload)
|
|
510
|
+
- an overload with no `@return`, when another overload of the method has one and the method docstring has none
|
|
511
|
+
|
|
512
|
+
```ruby
|
|
513
|
+
# Bad - reports: Overload 'fetch(key)' documents @param 'name', which is not in its signature
|
|
514
|
+
# Overload 'fetch(key)' has no @param for 'key'
|
|
515
|
+
# @overload fetch(key)
|
|
516
|
+
# @param name [String] the key
|
|
517
|
+
# @return [Object]
|
|
518
|
+
def fetch(*args); end
|
|
519
|
+
|
|
520
|
+
# Good
|
|
521
|
+
# @overload fetch(key)
|
|
522
|
+
# @param key [String] the key
|
|
523
|
+
# @return [Object]
|
|
524
|
+
def fetch(*args); end
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Overloads that forward arguments (`...`) or take anonymous `*`, `**` or `&` parameters are not checked for unknown names.
|
|
528
|
+
|
|
429
529
|
## Handling Non-Standard Types
|
|
430
530
|
|
|
431
531
|
By default `Tags/InvalidTypes` accepts all built-in Ruby classes, constants, and a set of YARD pseudo-types (`nil`, `true`, `false`, `self`, `void`, `Boolean`, `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.
|
|
@@ -522,7 +622,7 @@ Configuration:
|
|
|
522
622
|
-c, --config FILE Path to config file (default: .yard-lint.yml)
|
|
523
623
|
|
|
524
624
|
Output:
|
|
525
|
-
-f, --format FORMAT Output format (text, json, quickfix)
|
|
625
|
+
-f, --format FORMAT Output format (text, json, quickfix, sarif)
|
|
526
626
|
-q, --quiet Quiet mode (only show summary)
|
|
527
627
|
--stats Show documentation coverage statistics
|
|
528
628
|
--[no-]progress Show progress indicator (default: auto-detect TTY)
|
|
@@ -538,6 +638,7 @@ Diff Mode:
|
|
|
538
638
|
|
|
539
639
|
Validators:
|
|
540
640
|
--only VALIDATORS Run only specified validators (comma-separated)
|
|
641
|
+
--explain VALIDATOR Explain what a validator checks and how to configure it
|
|
541
642
|
|
|
542
643
|
Configuration Generation:
|
|
543
644
|
--init Generate .yard-lint.yml config file
|
|
@@ -546,6 +647,7 @@ Configuration Generation:
|
|
|
546
647
|
--force Force overwrite when using --init
|
|
547
648
|
--auto-gen-config Generate .yard-lint-todo.yml to silence existing violations
|
|
548
649
|
--regenerate-todo Regenerate .yard-lint-todo.yml (overwrites existing)
|
|
650
|
+
--check-todo Report .yard-lint-todo.yml entries that no longer match any offense
|
|
549
651
|
--exclude-limit N Min files before grouping into pattern (default: 15)
|
|
550
652
|
|
|
551
653
|
Information:
|
data/bin/yard-lint
CHANGED
|
@@ -18,7 +18,7 @@ begin
|
|
|
18
18
|
config_file = file
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
opts.on('-f', '--format FORMAT', 'Output format (text, json, quickfix)') do |format|
|
|
21
|
+
opts.on('-f', '--format FORMAT', 'Output format (text, json, quickfix, sarif)') do |format|
|
|
22
22
|
options[:format] = format
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -64,6 +64,10 @@ begin
|
|
|
64
64
|
options[:only] = validators.split(',').map(&:strip)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
opts.on('--explain VALIDATOR', 'Explain what a validator checks and how to configure it') do |validator|
|
|
68
|
+
options[:explain] = validator
|
|
69
|
+
end
|
|
70
|
+
|
|
67
71
|
opts.separator ''
|
|
68
72
|
opts.separator 'Other options:'
|
|
69
73
|
|
|
@@ -95,6 +99,10 @@ begin
|
|
|
95
99
|
options[:force] = true
|
|
96
100
|
end
|
|
97
101
|
|
|
102
|
+
opts.on('--check-todo', 'Report .yard-lint-todo.yml entries that no longer match any offense') do
|
|
103
|
+
options[:check_todo] = true
|
|
104
|
+
end
|
|
105
|
+
|
|
98
106
|
opts.on('--exclude-limit COUNT', Integer, 'Min files before grouping into pattern (default: 15)') do |count|
|
|
99
107
|
options[:exclude_limit] = count
|
|
100
108
|
end
|
|
@@ -112,17 +120,20 @@ begin
|
|
|
112
120
|
puts ' yard-lint lib/ # Lint all files in lib/'
|
|
113
121
|
puts ' yard-lint --only Tags/TypeSyntax lib/ # Run only one validator on lib/'
|
|
114
122
|
puts ' yard-lint --only Tags/Order,Tags/TypeSyntax # Run specific validators'
|
|
123
|
+
puts ' yard-lint --explain Tags/TypeSyntax # Explain what a validator checks'
|
|
115
124
|
puts ' yard-lint --diff main # Lint files changed since main branch'
|
|
116
125
|
puts ' yard-lint --staged # Lint only staged files'
|
|
117
126
|
puts ' yard-lint --changed # Lint only uncommitted files'
|
|
118
127
|
puts ' yard-lint --format json lib/ # Output in JSON format'
|
|
119
128
|
puts ' yard-lint --format quickfix lib/ # Output in quickfix format (Vim/Emacs)'
|
|
129
|
+
puts ' yard-lint --format sarif lib/ # Output SARIF 2.1.0 (GitHub code scanning)'
|
|
120
130
|
puts ' yard-lint --init # Generate default config'
|
|
121
131
|
puts ' yard-lint --init --strict # Generate strict config'
|
|
122
132
|
puts ' yard-lint --update # Update config with new validators'
|
|
123
133
|
puts ' yard-lint --auto-gen-config # Generate todo file for existing violations'
|
|
124
134
|
puts ' yard-lint --regenerate-todo # Regenerate todo file'
|
|
125
135
|
puts ' yard-lint --auto-gen-config --exclude-limit 10 # Custom grouping threshold'
|
|
136
|
+
puts ' yard-lint --check-todo # Fail if todo file has unused entries'
|
|
126
137
|
puts ' cat lib/foo.rb | yard-lint --stdin lib/foo.rb # Lint source piped from stdin'
|
|
127
138
|
exit
|
|
128
139
|
end
|
|
@@ -133,6 +144,62 @@ rescue OptionParser::ParseError => e
|
|
|
133
144
|
exit 1
|
|
134
145
|
end
|
|
135
146
|
|
|
147
|
+
# Report one or more unknown validator names with "did you mean" suggestions,
|
|
148
|
+
# the full list of available validators, and a pointer to --explain. Shared by
|
|
149
|
+
# --only and --explain so the two stay consistent.
|
|
150
|
+
report_unknown_validators = lambda do |names|
|
|
151
|
+
checker = DidYouMean::SpellChecker.new(dictionary: Yard::Lint::ConfigLoader::ALL_VALIDATORS)
|
|
152
|
+
messages = names.map do |name|
|
|
153
|
+
suggestions = checker.correct(name)
|
|
154
|
+
suggestions.any? ? " #{name} (did you mean: #{suggestions.first}?)" : " #{name}"
|
|
155
|
+
end
|
|
156
|
+
puts 'Error: Unknown validator(s):'
|
|
157
|
+
puts messages.join("\n")
|
|
158
|
+
puts "\nAvailable validators:"
|
|
159
|
+
Yard::Lint::ConfigLoader::ALL_VALIDATORS.each { |v| puts " #{v}" }
|
|
160
|
+
puts "\nRun `yard-lint --explain VALIDATOR` to see what a validator checks."
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Apply --only filter if provided, exiting on unknown validator names
|
|
164
|
+
apply_only = lambda do |config|
|
|
165
|
+
next unless options[:only]
|
|
166
|
+
|
|
167
|
+
unknown = options[:only] - Yard::Lint::ConfigLoader::ALL_VALIDATORS
|
|
168
|
+
if unknown.any?
|
|
169
|
+
report_unknown_validators.call(unknown)
|
|
170
|
+
exit 1
|
|
171
|
+
end
|
|
172
|
+
config.only_validators = options[:only]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Load the config (-c file or auto-discovered), exiting with a configuration error
|
|
176
|
+
# instead of a backtrace when it is missing, invalid or inherits in a cycle.
|
|
177
|
+
# Shared by the normal run, --auto-gen-config and --check-todo.
|
|
178
|
+
load_config = lambda do
|
|
179
|
+
if config_file
|
|
180
|
+
Yard::Lint::Config.from_file(config_file)
|
|
181
|
+
else
|
|
182
|
+
Yard::Lint::Config.load || Yard::Lint::Config.new
|
|
183
|
+
end
|
|
184
|
+
rescue Yard::Lint::Errors::ConfigFileNotFoundError, Yard::Lint::Errors::CircularDependencyError => e
|
|
185
|
+
puts "Configuration Error: #{e.message}"
|
|
186
|
+
exit 1
|
|
187
|
+
rescue Yard::Lint::Errors::InvalidConfigError => e
|
|
188
|
+
puts "Configuration Error:\n#{e.message}"
|
|
189
|
+
exit 1
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Handle --explain flag (early exit; needs no config or path)
|
|
193
|
+
if options[:explain]
|
|
194
|
+
name = options[:explain]
|
|
195
|
+
unless Yard::Lint::ConfigLoader::ALL_VALIDATORS.include?(name)
|
|
196
|
+
report_unknown_validators.call([name])
|
|
197
|
+
exit 1
|
|
198
|
+
end
|
|
199
|
+
puts Yard::Lint::Explainer.call(name)
|
|
200
|
+
exit 0
|
|
201
|
+
end
|
|
202
|
+
|
|
136
203
|
# Disambiguate `--diff PATH`. `--diff [REF]` takes an optional argument, so
|
|
137
204
|
# `yard-lint --diff lib/` makes OptionParser consume `lib/` as the REF, leaving
|
|
138
205
|
# no PATH (the run then tries `git diff lib/...HEAD` and fails). When the --diff
|
|
@@ -189,12 +256,7 @@ end
|
|
|
189
256
|
# Handle --auto-gen-config flag
|
|
190
257
|
if options[:auto_gen_config]
|
|
191
258
|
begin
|
|
192
|
-
|
|
193
|
-
config = if config_file
|
|
194
|
-
Yard::Lint::Config.from_file(config_file)
|
|
195
|
-
else
|
|
196
|
-
Yard::Lint::Config.load || Yard::Lint::Config.new
|
|
197
|
-
end
|
|
259
|
+
config = load_config.call
|
|
198
260
|
|
|
199
261
|
# Get path argument
|
|
200
262
|
path = ARGV[0] || '.'
|
|
@@ -222,6 +284,26 @@ if options[:auto_gen_config]
|
|
|
222
284
|
end
|
|
223
285
|
end
|
|
224
286
|
|
|
287
|
+
# Handle --check-todo flag
|
|
288
|
+
if options[:check_todo]
|
|
289
|
+
begin
|
|
290
|
+
config = load_config.call
|
|
291
|
+
apply_only.call(config)
|
|
292
|
+
|
|
293
|
+
result = Yard::Lint::TodoGenerator.check(path: ARGV[0] || '.', config: config)
|
|
294
|
+
|
|
295
|
+
puts result[:message]
|
|
296
|
+
exit result[:unused_entries].empty? ? 0 : 1
|
|
297
|
+
rescue Yard::Lint::Errors::TodoFileNotFoundError, Yard::Lint::Errors::FileNotFoundError,
|
|
298
|
+
Yard::Lint::Git::Error => e
|
|
299
|
+
puts "Error: #{e.message}"
|
|
300
|
+
exit 1
|
|
301
|
+
rescue Yard::Lint::Errors::InvalidConfigError => e
|
|
302
|
+
puts "Configuration Error:\n#{e.message}"
|
|
303
|
+
exit 1
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
225
307
|
# Get path argument (defaults to current directory)
|
|
226
308
|
path = ARGV[0] || '.'
|
|
227
309
|
|
|
@@ -239,51 +321,23 @@ end
|
|
|
239
321
|
YARD::Registry.clear
|
|
240
322
|
|
|
241
323
|
# Load config and apply CLI overrides
|
|
242
|
-
|
|
243
|
-
config = if config_file
|
|
244
|
-
Yard::Lint::Config.from_file(config_file)
|
|
245
|
-
else
|
|
246
|
-
Yard::Lint::Config.load || Yard::Lint::Config.new
|
|
247
|
-
end
|
|
248
|
-
rescue Yard::Lint::Errors::ConfigFileNotFoundError => e
|
|
249
|
-
puts "Configuration Error: #{e.message}"
|
|
250
|
-
exit 1
|
|
251
|
-
rescue Yard::Lint::Errors::InvalidConfigError => e
|
|
252
|
-
puts "Configuration Error:\n#{e.message}"
|
|
253
|
-
exit 1
|
|
254
|
-
end
|
|
324
|
+
config = load_config.call
|
|
255
325
|
|
|
256
326
|
# Apply CLI min_coverage override if provided
|
|
257
327
|
config.min_coverage = options[:min_coverage] if options[:min_coverage]
|
|
258
328
|
|
|
259
|
-
|
|
260
|
-
if options[:only]
|
|
261
|
-
unknown = options[:only] - Yard::Lint::ConfigLoader::ALL_VALIDATORS
|
|
262
|
-
if unknown.any?
|
|
263
|
-
checker = DidYouMean::SpellChecker.new(dictionary: Yard::Lint::ConfigLoader::ALL_VALIDATORS)
|
|
264
|
-
messages = unknown.map do |name|
|
|
265
|
-
suggestions = checker.correct(name)
|
|
266
|
-
suggestions.any? ? " #{name} (did you mean: #{suggestions.first}?)" : " #{name}"
|
|
267
|
-
end
|
|
268
|
-
puts "Error: Unknown validator(s):"
|
|
269
|
-
puts messages.join("\n")
|
|
270
|
-
puts "\nAvailable validators:"
|
|
271
|
-
Yard::Lint::ConfigLoader::ALL_VALIDATORS.each { |v| puts " #{v}" }
|
|
272
|
-
exit 1
|
|
273
|
-
end
|
|
274
|
-
config.only_validators = options[:only]
|
|
275
|
-
end
|
|
329
|
+
apply_only.call(config)
|
|
276
330
|
|
|
277
331
|
# Validate the output format before doing any (potentially long) work, rather
|
|
278
332
|
# than running the whole lint and only then rejecting an unknown format.
|
|
279
|
-
valid_formats = %w[text json quickfix]
|
|
333
|
+
valid_formats = %w[text json quickfix sarif]
|
|
280
334
|
if options[:format] && !valid_formats.include?(options[:format])
|
|
281
335
|
puts "Error: Unknown format '#{options[:format]}'. Valid formats: #{valid_formats.join(', ')}"
|
|
282
336
|
exit 1
|
|
283
337
|
end
|
|
284
338
|
|
|
285
|
-
# Suppress progress for
|
|
286
|
-
options[:progress] = false if options[:format]
|
|
339
|
+
# Suppress progress for machine-readable formats to avoid contaminating output
|
|
340
|
+
options[:progress] = false if %w[quickfix sarif].include?(options[:format]) && options[:progress].nil?
|
|
287
341
|
|
|
288
342
|
# Run the linter
|
|
289
343
|
begin
|
|
@@ -332,6 +386,12 @@ when 'quickfix'
|
|
|
332
386
|
puts "#{offense[:location]}:#{offense[:location_line]}: #{char}: #{offense[:validator]}: #{message}"
|
|
333
387
|
end
|
|
334
388
|
exit result.exit_code
|
|
389
|
+
when 'sarif'
|
|
390
|
+
# Base paths on the repository root so GitHub can map offense URIs onto files
|
|
391
|
+
# even when yard-lint is run from a subdirectory.
|
|
392
|
+
sarif = Yard::Lint::Formatters::Sarif.new(result.offenses, base_dir: Yard::Lint::Git.repository_root)
|
|
393
|
+
puts sarif.generate
|
|
394
|
+
exit result.exit_code
|
|
335
395
|
when 'text', nil
|
|
336
396
|
# Calculate coverage stats if requested or configured
|
|
337
397
|
coverage = result.documentation_coverage if options[:stats] || options[:min_coverage] || config.min_coverage
|
data/lib/yard/lint/config.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Yard
|
|
|
6
6
|
class Config
|
|
7
7
|
attr_reader :raw_config, :validators
|
|
8
8
|
attr_accessor :only_validators
|
|
9
|
+
attr_writer :base_dir
|
|
9
10
|
|
|
10
11
|
# Default YAML config file name
|
|
11
12
|
DEFAULT_CONFIG_FILE = '.yard-lint.yml'
|
|
@@ -16,6 +17,9 @@ module Yard
|
|
|
16
17
|
# Metadata keys to skip when merging validator configs
|
|
17
18
|
METADATA_KEYS = %w[Description StyleGuide VersionAdded VersionChanged].freeze
|
|
18
19
|
|
|
20
|
+
# File.fnmatch flags used for Exclude patterns
|
|
21
|
+
EXCLUDE_FLAGS = File::FNM_PATHNAME | File::FNM_EXTGLOB
|
|
22
|
+
|
|
19
23
|
# @param raw_config [Hash] raw configuration hash (new hierarchical format)
|
|
20
24
|
def initialize(raw_config = {})
|
|
21
25
|
@raw_config = raw_config
|
|
@@ -42,7 +46,7 @@ module Yard
|
|
|
42
46
|
# Load with inheritance support
|
|
43
47
|
merged_yaml = ConfigLoader.load(path)
|
|
44
48
|
|
|
45
|
-
new(merged_yaml)
|
|
49
|
+
new(merged_yaml) { |config| config.base_dir = File.dirname(File.expand_path(path)) }
|
|
46
50
|
end
|
|
47
51
|
|
|
48
52
|
# Search for and load config file from current directory upwards
|
|
@@ -173,29 +177,101 @@ module Yard
|
|
|
173
177
|
end
|
|
174
178
|
|
|
175
179
|
# Get validator severity
|
|
180
|
+
#
|
|
181
|
+
# Resolution order (highest priority first):
|
|
182
|
+
# 1. an explicit per-validator Severity in the user's config
|
|
183
|
+
# 2. a category-level Severity (e.g. `Documentation: { Severity: convention }`),
|
|
184
|
+
# matching how category-level Enabled cascades in #validator_enabled?
|
|
185
|
+
# 3. the validator's built-in default Severity
|
|
186
|
+
# 4. the global 'warning' default
|
|
187
|
+
#
|
|
176
188
|
# @param validator_name [String] full validator name
|
|
177
189
|
# @return [String] severity level for this validator
|
|
178
190
|
def validator_severity(validator_name)
|
|
191
|
+
# An explicit per-validator Severity in the user's config wins.
|
|
192
|
+
raw_validator = @raw_config[validator_name]
|
|
193
|
+
if raw_validator.is_a?(Hash) && raw_validator['Severity']
|
|
194
|
+
return raw_validator['Severity'].to_s
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Otherwise honor a category-level Severity, which previously validated
|
|
198
|
+
# but was ignored (the category-level Enabled key already cascades).
|
|
199
|
+
category = validator_name.split('/').first
|
|
200
|
+
raw_category = @raw_config[category]
|
|
201
|
+
if raw_category.is_a?(Hash) && raw_category['Severity']
|
|
202
|
+
return raw_category['Severity'].to_s
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Fall back to the validator's built-in default, then the global default.
|
|
179
206
|
validator_config = validators[validator_name] || {}
|
|
180
207
|
validator_config['Severity'] || 'warning'
|
|
181
208
|
end
|
|
182
209
|
|
|
183
210
|
# Get validator-specific exclude patterns
|
|
211
|
+
#
|
|
212
|
+
# A category-level Exclude (e.g. `Documentation: { Exclude: [...] }`) is
|
|
213
|
+
# unioned with the per-validator one rather than replaced by it, so a
|
|
214
|
+
# validator that sets its own Exclude still skips its category's files.
|
|
215
|
+
#
|
|
184
216
|
# @param validator_name [String] full validator name
|
|
185
|
-
# @return [Array<String>] exclusion patterns for this validator
|
|
217
|
+
# @return [Array<String>] exclusion patterns for this validator (category + per-validator)
|
|
186
218
|
def validator_exclude(validator_name)
|
|
187
219
|
validator_config = validators[validator_name] || {}
|
|
188
|
-
|
|
220
|
+
raw_category = @raw_config[validator_name.split('/').first]
|
|
221
|
+
category_exclude = raw_category.is_a?(Hash) ? Array(raw_category['Exclude']) : []
|
|
222
|
+
|
|
223
|
+
(category_exclude + (validator_config['Exclude'] || [])).uniq
|
|
189
224
|
end
|
|
190
225
|
|
|
191
|
-
# Combined global and per-validator exclusions
|
|
226
|
+
# Combined global, category and per-validator exclusions
|
|
192
227
|
# Returns all exclusion patterns that apply to this validator
|
|
193
228
|
# @param validator_name [String] full validator name
|
|
194
|
-
# @return [Array<String>] combined exclusion patterns (global + per-validator)
|
|
229
|
+
# @return [Array<String>] combined exclusion patterns (global + category + per-validator)
|
|
195
230
|
def validator_all_excludes(validator_name)
|
|
196
231
|
exclude + validator_exclude(validator_name)
|
|
197
232
|
end
|
|
198
233
|
|
|
234
|
+
# Directory that Exclude patterns are relative to
|
|
235
|
+
# @return [String] directory of the loaded config file, or the current directory without one
|
|
236
|
+
def base_dir
|
|
237
|
+
@base_dir || Dir.pwd
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Whether a file matches any of the given Exclude patterns
|
|
241
|
+
# A pattern matches the path relative to {#base_dir} or the absolute path
|
|
242
|
+
# @param file [String] absolute file path, or one relative to the current directory
|
|
243
|
+
# @param patterns [Array<String>] glob patterns
|
|
244
|
+
# @return [Boolean] true if any pattern matches
|
|
245
|
+
def excluded_path?(file, patterns)
|
|
246
|
+
absolute = File.expand_path(file)
|
|
247
|
+
relative = relative_path(absolute)
|
|
248
|
+
|
|
249
|
+
patterns.any? do |pattern|
|
|
250
|
+
File.fnmatch(pattern, relative, EXCLUDE_FLAGS) || File.fnmatch(pattern, absolute, EXCLUDE_FLAGS)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Path of a file relative to {#base_dir}
|
|
255
|
+
# Symlinks are resolved when the plain paths do not nest (/var is /private/var on macOS)
|
|
256
|
+
# @param file [String] absolute file path, or one relative to the current directory
|
|
257
|
+
# @return [String] relative path (with ../ for a file outside {#base_dir}), or the absolute
|
|
258
|
+
# path when there is no relative one (another drive)
|
|
259
|
+
def relative_path(file)
|
|
260
|
+
absolute = File.expand_path(file)
|
|
261
|
+
base = File.expand_path(base_dir)
|
|
262
|
+
return absolute.delete_prefix("#{base}/") if absolute.start_with?("#{base}/")
|
|
263
|
+
|
|
264
|
+
from, to = begin
|
|
265
|
+
[File.realpath(absolute), File.realpath(base)]
|
|
266
|
+
rescue SystemCallError
|
|
267
|
+
[absolute, base]
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
Pathname.new(from).relative_path_from(Pathname.new(to)).to_s
|
|
271
|
+
rescue ArgumentError
|
|
272
|
+
absolute
|
|
273
|
+
end
|
|
274
|
+
|
|
199
275
|
# Get validator-specific configuration value
|
|
200
276
|
# @param validator_name [String] full validator name
|
|
201
277
|
# @param key [String] configuration key
|