yard-lint 1.10.0 → 1.10.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37286ca0e1a4253fd44af81beda164e713f58903e9d7c06ccf7dc06484c76be9
4
- data.tar.gz: 2b5b3dc5eaed758b12b07a5a2a67ad537dbf56e8459a5075e796850a73deb2a2
3
+ metadata.gz: e1b9f3a39ce5fb15c2a1ace664e45d393fb70e429220085ebd4a7881aa2496cc
4
+ data.tar.gz: 207fb47ddd283bd62755c6f5736652166caed19a342f601ce78cfe73c0aad5e9
5
5
  SHA512:
6
- metadata.gz: ca788e4ee2a91c5990b20c953bf28bfeaa731c6feaa4183a3bb323e04f5a198af40a7f30d60e4a714d9361a0ab589a9c202f766202e7c6468ad7c7407b55d5ea
7
- data.tar.gz: a38ddad77479fdcc11f18d530fafacd090d079827a1e93ad423f2dc839e5e50cc072ab04a7a2150836f4f654f560e098abf6e59c5b38d970f6d93d39a373b8cf
6
+ metadata.gz: 7898934b15acdea1b39a244e6224969cef3848427b02db1711c87480ff8459b481920eb6a378fe25296bf0c2dc708d7f3b63ec7c8bd25effb7ea92d8c21179df
7
+ data.tar.gz: cb7525e59baebfe5cbb1e192ed91d38c6c75d04a4fd1ed9c60db1616794e66b67b46b39cefd5fa734565b30bb7bb4419fe672cbe798b2019443c0d5f59e7f7ae
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.10.2 (2026-07-24)
2
+ - **[Bugfix]** `Semantic/AbstractMethods` no longer flags an `@abstract` method whose body is `fail NotImplementedError`. `fail` is a built-in alias of `raise` (`Kernel#fail`), so `fail NotImplementedError` is an identical abstract-method guard to `raise NotImplementedError`, but the `AllowedImplementations` patterns are written with `raise` and were matched literally. A leading `fail` keyword is now normalized to `raise` before matching, so both forms are recognized (and any custom `raise`-based `AllowedImplementations` pattern automatically covers its `fail` alias). Identifiers such as `failure` and non-leading `fail(...)` calls are left untouched.
3
+
4
+ ## 1.10.1 (2026-07-24)
5
+ - **[Bugfix]** `Documentation/BlankLineBeforeDefinition` no longer reports a foreign comment block above a definition as documentation that was accidentally detached from it. When the object is documented (non-empty docstring) but that docstring did not come from the comment block sitting above this definition, the block is not a detached docstring - it is a file-level license/copyright banner, an encoding note, or an unrelated comment above a namespace reopening whose documentation lives in another file. The check compares the object's docstring against the block's text, so it recognizes any such banner without hardcoding its wording. Definitions whose documentation was genuinely lost (an undocumented object with a comment block detached above it) are still reported.
6
+
1
7
  ## 1.10.0 (2026-07-24)
2
8
  - **[Feature]** Added `Documentation/DuplicateNamespaceComment` (enabled by default, severity `warning`) - detects namespaces (modules and classes) that carry a YARD documentation comment in more than one file. When a namespace is reopened across files and documented in several of them, YARD merges the reopenings into a single object and keeps only one docstring, silently discarding the rest (the last documented reopening wins, with no warning), so competing descriptions are lost. This is a common accident for shared namespaces (e.g. `Users` or `Users::Operations`) spread across many files; it does not affect a leaf object such as `Users::Operations::Create` that lives in a single file. Because YARD does not record which reopening carried a comment, the validator re-reads each definition site to detect the documented ones (ignoring blank-line-detached comments and directive/magic comments), and reports one offense per namespace documented in two or more files, listing every documented location and noting when the docstrings differ (meaning content is actually lost). Configurable via the standard `Enabled` and `Severity` keys.
3
9
 
@@ -21,9 +21,19 @@ module Yard
21
21
  source_lines = File.readlines(object.file)
22
22
  definition_line = object.line - 1
23
23
 
24
- blank_count, has_doc_block = analyze_spacing(source_lines, definition_line)
24
+ blank_count, doc_block_line = analyze_spacing(source_lines, definition_line)
25
25
 
26
- return if blank_count.zero? || !has_doc_block
26
+ return if blank_count.zero? || doc_block_line.nil?
27
+
28
+ # Only a comment block that is genuinely THIS object's documentation can have
29
+ # been detached from it by the blank line. When the object is documented (its
30
+ # docstring is non-empty) but that docstring did not come from the comment block
31
+ # sitting above this definition, the block is a foreign comment - a file-level
32
+ # license/copyright banner, an encoding note, or an unrelated comment above a
33
+ # namespace reopening that is documented in another file - and the blank line is
34
+ # intentional. This is decided by comparing the object's docstring against the
35
+ # block's text, so it covers any such banner without hardcoding its wording.
36
+ return if foreign_comment_block?(object, source_lines, doc_block_line)
27
37
 
28
38
  violation_type = blank_count >= 2 ? 'orphaned' : 'single'
29
39
 
@@ -38,10 +48,10 @@ module Yard
38
48
  # Analyze spacing between documentation and definition
39
49
  # @param source_lines [Array<String>] lines of source file
40
50
  # @param definition_line [Integer] 0-indexed line of definition
41
- # @return [Array<Integer, Boolean>] blank count and whether doc block exists
51
+ # @return [Array<Integer, Integer, nil>] blank count and the 0-indexed line of the
52
+ # nearest documentation comment above the definition (nil when there is none)
42
53
  def analyze_spacing(source_lines, definition_line)
43
54
  blank_count = 0
44
- has_doc_block = false
45
55
 
46
56
  (definition_line - 1).downto(0) do |i|
47
57
  line = source_lines[i].to_s.rstrip
@@ -56,15 +66,65 @@ module Yard
56
66
  # spurious blank-line offenses for undocumented definitions.
57
67
  next if non_documentation_comment?(stripped)
58
68
 
59
- has_doc_block = true
60
- break
69
+ return [blank_count, i]
61
70
  else
62
71
  # Non-comment, non-blank line - no documentation above
63
72
  break
64
73
  end
65
74
  end
66
75
 
67
- [blank_count, has_doc_block]
76
+ [blank_count, nil]
77
+ end
78
+
79
+ # Whether the comment block above the definition is NOT the source of the object's
80
+ # documentation - a file banner or an unrelated comment rather than a docstring
81
+ # this definition lost to the blank line.
82
+ #
83
+ # An object with no docstring at all has genuinely orphaned documentation, so the
84
+ # block above is still reported. When the object IS documented, the block only
85
+ # counts as detached documentation if the docstring's text actually came from it;
86
+ # a license header or a comment above a reopening documented elsewhere carries
87
+ # different text and is left alone.
88
+ # @param object [YARD::CodeObjects::Base] the code object being checked
89
+ # @param source_lines [Array<String>] lines of source file
90
+ # @param doc_line [Integer] 0-indexed line of the nearest comment above the definition
91
+ # @return [Boolean]
92
+ def foreign_comment_block?(object, source_lines, doc_line)
93
+ docstring = object.docstring.to_s.strip
94
+ return false if docstring.empty?
95
+
96
+ block = comment_block_text(source_lines, doc_line)
97
+ first_line = normalize(docstring.lines.first.to_s)
98
+ return false if first_line.empty?
99
+
100
+ !normalize(block).include?(first_line)
101
+ end
102
+
103
+ # Text of the contiguous comment block that contains `doc_line`, stripped of the
104
+ # comment markers and joined into a single string.
105
+ # @param source_lines [Array<String>] lines of source file
106
+ # @param doc_line [Integer] 0-indexed line within the comment block
107
+ # @return [String]
108
+ def comment_block_text(source_lines, doc_line)
109
+ first = doc_line
110
+ first -= 1 while first.positive? && comment_line?(source_lines[first - 1])
111
+ last = doc_line
112
+ last += 1 while last + 1 < source_lines.length && comment_line?(source_lines[last + 1])
113
+
114
+ source_lines[first..last].map { |line| line.sub(/\A\s*#+\s?/, '').strip }.join(' ')
115
+ end
116
+
117
+ # @param line [String, nil] a source line
118
+ # @return [Boolean] whether the line is a comment line
119
+ def comment_line?(line)
120
+ line.to_s.strip.start_with?('#')
121
+ end
122
+
123
+ # Collapse whitespace so wrapped docstrings and their source comments compare equal.
124
+ # @param text [String]
125
+ # @return [String]
126
+ def normalize(text)
127
+ text.strip.gsub(/\s+/, ' ')
68
128
  end
69
129
 
70
130
  # Check if a comment line is a Ruby magic comment
@@ -46,7 +46,7 @@ module Yard
46
46
  has_real_implementation = body_lines.any? do |line|
47
47
  next false if line.start_with?('#') || line == 'end'
48
48
 
49
- allowed.none? { |pattern| line.match?(pattern) }
49
+ allowed.none? { |pattern| normalize_raise_alias(line).match?(pattern) }
50
50
  end
51
51
 
52
52
  return unless has_real_implementation
@@ -69,6 +69,18 @@ module Yard
69
69
  end
70
70
  end
71
71
 
72
+ # Rewrites a leading `fail` keyword to `raise` so the two are treated alike when
73
+ # matching AllowedImplementations. `fail` is a built-in alias of `raise`
74
+ # (`Kernel#fail`), so `fail NotImplementedError` is an identical abstract-method
75
+ # guard to `raise NotImplementedError`, but the configured patterns are written
76
+ # with `raise`. Only a leading keyword is rewritten, so identifiers such as
77
+ # `failure` or a `fail(...)` call elsewhere in a line are left untouched.
78
+ # @param line [String] a stripped body line
79
+ # @return [String]
80
+ def normalize_raise_alias(line)
81
+ line.sub(/\Afail\b/, 'raise')
82
+ end
83
+
72
84
  # Joins lines that are continuations of the previous statement (the
73
85
  # previous line ends with a comma or a backslash) into a single
74
86
  # logical line.
@@ -3,6 +3,6 @@
3
3
  module Yard
4
4
  module Lint
5
5
  # @return [String] version of the YARD Lint gem
6
- VERSION = '1.10.0'
6
+ VERSION = '1.10.2'
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yard-lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 1.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld