yard-lint 1.2.2 → 1.3.0.rc1
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 +174 -1
- data/README.md +118 -3
- data/Rakefile +20 -0
- data/bin/yard-lint +80 -37
- data/lib/yard/lint/config.rb +5 -0
- data/lib/yard/lint/config_generator.rb +8 -179
- data/lib/yard/lint/config_updater.rb +222 -0
- data/lib/yard/lint/errors.rb +6 -0
- data/lib/yard/lint/executor/in_process_registry.rb +130 -0
- data/lib/yard/lint/executor/query_executor.rb +109 -0
- data/lib/yard/lint/executor/result_collector.rb +55 -0
- data/lib/yard/lint/executor/warning_dispatcher.rb +79 -0
- data/lib/yard/lint/results/base.rb +2 -1
- data/lib/yard/lint/runner.rb +88 -35
- data/lib/yard/lint/stats_calculator.rb +1 -1
- data/lib/yard/lint/templates/default_config.yml +279 -0
- data/lib/yard/lint/templates/strict_config.yml +283 -0
- data/lib/yard/lint/validators/base.rb +52 -118
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/config.rb +25 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/messages_builder.rb +39 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/parser.rb +59 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/result.rb +61 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/validator.rb +94 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition.rb +63 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/config.rb +24 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/messages_builder.rb +34 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/parser.rb +60 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/result.rb +25 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/validator.rb +109 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line.rb +58 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +36 -21
- data/lib/yard/lint/validators/documentation/markdown_syntax.rb +0 -1
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +19 -29
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +0 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +18 -34
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +0 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +2 -2
- data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +17 -25
- data/lib/yard/lint/validators/documentation/undocumented_objects.rb +4 -5
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +30 -21
- data/lib/yard/lint/validators/documentation/undocumented_options.rb +0 -1
- data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +2 -2
- data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +31 -43
- data/lib/yard/lint/validators/semantic/abstract_methods.rb +0 -1
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +24 -39
- data/lib/yard/lint/validators/tags/api_tags.rb +0 -1
- data/lib/yard/lint/validators/tags/collection_type/parser.rb +1 -1
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +37 -66
- data/lib/yard/lint/validators/tags/collection_type.rb +0 -1
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +51 -64
- data/lib/yard/lint/validators/tags/example_syntax.rb +0 -1
- data/lib/yard/lint/validators/tags/informal_notation/config.rb +40 -0
- data/lib/yard/lint/validators/tags/informal_notation/messages_builder.rb +35 -0
- data/lib/yard/lint/validators/tags/informal_notation/parser.rb +55 -0
- data/lib/yard/lint/validators/tags/informal_notation/result.rb +26 -0
- data/lib/yard/lint/validators/tags/informal_notation/validator.rb +133 -0
- data/lib/yard/lint/validators/tags/informal_notation.rb +45 -0
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +57 -70
- data/lib/yard/lint/validators/tags/invalid_types.rb +0 -1
- data/lib/yard/lint/validators/tags/meaningless_tag/parser.rb +1 -1
- data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +22 -54
- data/lib/yard/lint/validators/tags/meaningless_tag.rb +0 -1
- data/lib/yard/lint/validators/tags/non_ascii_type/config.rb +21 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/messages_builder.rb +29 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/parser.rb +59 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/result.rb +25 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +50 -0
- data/lib/yard/lint/validators/tags/non_ascii_type.rb +39 -0
- data/lib/yard/lint/validators/tags/option_tags/result.rb +2 -2
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +25 -40
- data/lib/yard/lint/validators/tags/option_tags.rb +0 -1
- data/lib/yard/lint/validators/tags/order/validator.rb +28 -55
- data/lib/yard/lint/validators/tags/order.rb +0 -1
- data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +15 -1
- data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +5 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +134 -100
- data/lib/yard/lint/validators/tags/redundant_param_description.rb +0 -1
- data/lib/yard/lint/validators/tags/tag_group_separator/config.rb +29 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/messages_builder.rb +49 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +67 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/result.rb +28 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +117 -0
- data/lib/yard/lint/validators/tags/tag_group_separator.rb +49 -0
- data/lib/yard/lint/validators/tags/tag_type_position/parser.rb +1 -1
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +53 -84
- data/lib/yard/lint/validators/tags/tag_type_position.rb +4 -5
- data/lib/yard/lint/validators/tags/type_syntax/parser.rb +8 -3
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +29 -59
- data/lib/yard/lint/validators/tags/type_syntax.rb +0 -1
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/messages_builder.rb +243 -0
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/result.rb +4 -3
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_tag/messages_builder.rb +144 -0
- data/lib/yard/lint/validators/warnings/unknown_tag/result.rb +4 -3
- data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_tag.rb +10 -0
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +81 -13
- data/renovate.json +1 -8
- metadata +40 -6
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/lib/yard/lint/command_cache.rb +0 -93
|
@@ -7,78 +7,65 @@ module Yard
|
|
|
7
7
|
module ExampleSyntax
|
|
8
8
|
# Validator to check syntax of code in @example tags
|
|
9
9
|
class Validator < Base
|
|
10
|
-
|
|
10
|
+
# Enable in-process execution with all visibility
|
|
11
|
+
in_process visibility: :all
|
|
11
12
|
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# @param
|
|
15
|
-
# @
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
--private \
|
|
20
|
-
--protected \
|
|
21
|
-
-b #{Shellwords.escape(dir)}
|
|
22
|
-
CMD
|
|
23
|
-
cmd = cmd.tr("\n", ' ')
|
|
24
|
-
cmd = cmd.gsub('yard list', "yard list --query #{query}")
|
|
13
|
+
# Execute query for a single object during in-process execution.
|
|
14
|
+
# Checks syntax of code in @example tags.
|
|
15
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
16
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
17
|
+
# @return [void]
|
|
18
|
+
def in_process_query(object, collector)
|
|
19
|
+
return unless object.has_tag?(:example)
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# @return [String] yard query to find @example tags with syntax errors
|
|
30
|
-
def query
|
|
31
|
-
<<~QUERY
|
|
32
|
-
'
|
|
33
|
-
if object.has_tag?(:example)
|
|
34
|
-
example_tags = object.tags(:example)
|
|
21
|
+
example_tags = object.tags(:example)
|
|
35
22
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
example_tags.each_with_index do |example, index|
|
|
24
|
+
code = example.text
|
|
25
|
+
next if code.nil? || code.empty?
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
27
|
+
# Clean the code: strip output indicators (#=>) and everything after it
|
|
28
|
+
code_lines = code.split("\n").map do |line|
|
|
29
|
+
line.sub(/\s*#\s*=>.*$/, '')
|
|
30
|
+
end
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
|
|
32
|
+
cleaned_code = code_lines.join("\n").strip
|
|
33
|
+
next if cleaned_code.empty?
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
lines = cleaned_code.split("\\n").reject { |l| l.strip.empty? || l.strip.start_with?("#") }
|
|
35
|
+
# Check if code looks incomplete (single expression without context)
|
|
36
|
+
lines = cleaned_code.split("\n").reject { |l| l.strip.empty? || l.strip.start_with?('#') }
|
|
51
37
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
end
|
|
60
|
-
end
|
|
38
|
+
# Skip if it is a single line that looks like an incomplete expression
|
|
39
|
+
if lines.size == 1
|
|
40
|
+
line = lines.first.strip
|
|
41
|
+
# Skip method calls, variable references, or simple expressions
|
|
42
|
+
next if line.match?(/^[a-z_][a-z0-9_]*(\.| |$)/) ||
|
|
43
|
+
(line.match?(/^[A-Z]/) && !line.match?(/^(class|module|def)\s/))
|
|
44
|
+
end
|
|
61
45
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
46
|
+
# Try to parse the code
|
|
47
|
+
# Suppress Ruby parser warnings (like "... at EOL, should be parenthesized?")
|
|
48
|
+
# that can occur during compilation of valid but stylistically questionable code
|
|
49
|
+
original_verbose = $VERBOSE
|
|
50
|
+
begin
|
|
51
|
+
$VERBOSE = nil
|
|
52
|
+
RubyVM::InstructionSequence.compile(cleaned_code)
|
|
53
|
+
rescue SyntaxError => e
|
|
54
|
+
example_name = example.name || "Example #{index + 1}"
|
|
55
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
56
|
+
collector.puts 'syntax_error'
|
|
57
|
+
collector.puts example_name
|
|
58
|
+
collector.puts e.message
|
|
59
|
+
rescue ScriptError, EncodingError => e
|
|
60
|
+
# Non-syntax script errors (LoadError, NotImplementedError) and encoding
|
|
61
|
+
# issues should be logged but not reported as syntax errors.
|
|
62
|
+
# We only validate syntax, not runtime semantics or encoding validity.
|
|
63
|
+
warn "[YARD::Lint] Example code error in #{object.path}: #{e.class}: #{e.message}" if ENV['DEBUG']
|
|
64
|
+
next
|
|
65
|
+
ensure
|
|
66
|
+
$VERBOSE = original_verbose
|
|
67
|
+
end
|
|
68
|
+
end
|
|
82
69
|
end
|
|
83
70
|
end
|
|
84
71
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module InformalNotation
|
|
8
|
+
# Configuration for InformalNotation validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :informal_notation
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'warning',
|
|
14
|
+
'CaseSensitive' => false,
|
|
15
|
+
'RequireStartOfLine' => true,
|
|
16
|
+
'Patterns' => {
|
|
17
|
+
'Note' => '@note',
|
|
18
|
+
'IMPORTANT' => '@note',
|
|
19
|
+
'Important' => '@note',
|
|
20
|
+
'Todo' => '@todo',
|
|
21
|
+
'TODO' => '@todo',
|
|
22
|
+
'FIXME' => '@todo',
|
|
23
|
+
'See' => '@see',
|
|
24
|
+
'See also' => '@see',
|
|
25
|
+
'Warning' => '@deprecated',
|
|
26
|
+
'Deprecated' => '@deprecated',
|
|
27
|
+
'Author' => '@author',
|
|
28
|
+
'Version' => '@version',
|
|
29
|
+
'Since' => '@since',
|
|
30
|
+
'Returns' => '@return',
|
|
31
|
+
'Raises' => '@raise',
|
|
32
|
+
'Example' => '@example'
|
|
33
|
+
}
|
|
34
|
+
}.freeze
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module InformalNotation
|
|
8
|
+
# Builds human-readable messages for InformalNotation violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Formats an informal notation violation message
|
|
12
|
+
# @param offense [Hash] offense details with :pattern, :replacement, :line_text keys
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
pattern = offense[:pattern]
|
|
16
|
+
replacement = offense[:replacement]
|
|
17
|
+
line_text = offense[:line_text]
|
|
18
|
+
|
|
19
|
+
message = "Use #{replacement} tag instead of '#{pattern}:' notation"
|
|
20
|
+
|
|
21
|
+
if line_text && !line_text.empty?
|
|
22
|
+
# Truncate long lines for readability
|
|
23
|
+
truncated = line_text.length > 60 ? "#{line_text[0..57]}..." : line_text
|
|
24
|
+
message += ". Found: \"#{truncated}\""
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
message
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module InformalNotation
|
|
8
|
+
# Parser for InformalNotation validator output
|
|
9
|
+
# Parses YARD query output that reports informal notation violations
|
|
10
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
11
|
+
# Parses YARD output and extracts informal notation violations
|
|
12
|
+
# Expected format (two lines per violation):
|
|
13
|
+
# file.rb:LINE: ObjectName
|
|
14
|
+
# pattern|replacement|line_offset|line_text
|
|
15
|
+
# @param yard_output [String] raw YARD query results
|
|
16
|
+
# @option _kwargs [Object] :unused this parameter accepts no options (reserved for future use)
|
|
17
|
+
# @return [Array<Hash>] array with violation details
|
|
18
|
+
def call(yard_output, **_kwargs)
|
|
19
|
+
return [] if yard_output.nil? || yard_output.strip.empty?
|
|
20
|
+
|
|
21
|
+
lines = yard_output.split("\n").map(&:strip).reject(&:empty?)
|
|
22
|
+
violations = []
|
|
23
|
+
|
|
24
|
+
lines.each_slice(2) do |location_line, details_line|
|
|
25
|
+
next unless location_line && details_line
|
|
26
|
+
|
|
27
|
+
# Parse location: "file.rb:10: ObjectName"
|
|
28
|
+
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
29
|
+
next unless location_match
|
|
30
|
+
|
|
31
|
+
# Parse details: "pattern|replacement|line_offset|line_text"
|
|
32
|
+
details = details_line.split('|', 4)
|
|
33
|
+
next unless details.size >= 3
|
|
34
|
+
|
|
35
|
+
pattern, replacement, line_offset_str, line_text = details
|
|
36
|
+
|
|
37
|
+
violations << {
|
|
38
|
+
location: location_match[1],
|
|
39
|
+
line: location_match[2].to_i,
|
|
40
|
+
object_name: location_match[3],
|
|
41
|
+
pattern: pattern,
|
|
42
|
+
replacement: replacement,
|
|
43
|
+
line_offset: line_offset_str.to_i,
|
|
44
|
+
line_text: line_text || ''
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
violations
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module InformalNotation
|
|
8
|
+
# Result wrapper for InformalNotation validator
|
|
9
|
+
# Formats parsed violations into offense objects
|
|
10
|
+
class Result < Results::Base
|
|
11
|
+
self.default_severity = 'warning'
|
|
12
|
+
self.offense_type = 'line'
|
|
13
|
+
self.offense_name = 'InformalNotation'
|
|
14
|
+
|
|
15
|
+
# Builds a human-readable message for the offense
|
|
16
|
+
# @param offense [Hash] offense details
|
|
17
|
+
# @return [String] formatted message
|
|
18
|
+
def build_message(offense)
|
|
19
|
+
MessagesBuilder.call(offense)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module InformalNotation
|
|
8
|
+
# Validates that documentation uses proper YARD tags instead of
|
|
9
|
+
# informal notation patterns like "Note:", "TODO:", etc.
|
|
10
|
+
class Validator < Base
|
|
11
|
+
# Enable in-process execution with public visibility
|
|
12
|
+
in_process visibility: :public
|
|
13
|
+
|
|
14
|
+
# Execute query for a single object during in-process execution.
|
|
15
|
+
# Checks for informal notation patterns in docstrings.
|
|
16
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
17
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
18
|
+
# @return [void]
|
|
19
|
+
def in_process_query(object, collector)
|
|
20
|
+
docstring_text = object.docstring.to_s
|
|
21
|
+
return if docstring_text.empty?
|
|
22
|
+
|
|
23
|
+
patterns = config_patterns
|
|
24
|
+
case_sensitive = config_case_sensitive
|
|
25
|
+
require_start = config_require_start_of_line
|
|
26
|
+
|
|
27
|
+
found_patterns = find_informal_patterns(
|
|
28
|
+
docstring_text,
|
|
29
|
+
patterns,
|
|
30
|
+
case_sensitive,
|
|
31
|
+
require_start
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return if found_patterns.empty?
|
|
35
|
+
|
|
36
|
+
# Output format: two lines per violation
|
|
37
|
+
# Line 1: file:line: object_title
|
|
38
|
+
# Line 2: pattern|replacement|line_offset|line_text (pipe-separated)
|
|
39
|
+
found_patterns.each do |match|
|
|
40
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
41
|
+
collector.puts "#{match[:pattern]}|#{match[:replacement]}|" \
|
|
42
|
+
"#{match[:line_offset]}|#{match[:line_text]}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Find informal patterns in docstring text, skipping code blocks
|
|
49
|
+
# @param docstring_text [String] the docstring text to scan
|
|
50
|
+
# @param patterns [Hash] pattern => replacement mapping
|
|
51
|
+
# @param case_sensitive [Boolean] whether to match case-sensitively
|
|
52
|
+
# @param require_start [Boolean] whether pattern must be at start of line
|
|
53
|
+
# @return [Array<Hash>] array of matches with pattern, replacement, line_offset, line_text
|
|
54
|
+
def find_informal_patterns(docstring_text, patterns, case_sensitive, require_start)
|
|
55
|
+
found_patterns = []
|
|
56
|
+
matched_lines = Set.new
|
|
57
|
+
in_code_block = false
|
|
58
|
+
|
|
59
|
+
docstring_text.lines.each_with_index do |line, line_offset|
|
|
60
|
+
# Track fenced code blocks (``` markers)
|
|
61
|
+
if line.strip.start_with?('```')
|
|
62
|
+
in_code_block = !in_code_block
|
|
63
|
+
next
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Skip lines inside code blocks
|
|
67
|
+
next if in_code_block
|
|
68
|
+
|
|
69
|
+
# Skip lines already matched (avoids duplicate reports for similar patterns)
|
|
70
|
+
next if matched_lines.include?(line_offset)
|
|
71
|
+
|
|
72
|
+
patterns.each do |pattern, replacement|
|
|
73
|
+
next if replacement.nil? || replacement.empty?
|
|
74
|
+
next unless matches_pattern?(line, pattern, case_sensitive, require_start)
|
|
75
|
+
|
|
76
|
+
found_patterns << {
|
|
77
|
+
pattern: pattern,
|
|
78
|
+
replacement: replacement,
|
|
79
|
+
line_offset: line_offset,
|
|
80
|
+
line_text: line.strip
|
|
81
|
+
}
|
|
82
|
+
matched_lines.add(line_offset)
|
|
83
|
+
break
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
found_patterns
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Check if a line matches the informal pattern
|
|
91
|
+
# @param line [String] the line to check
|
|
92
|
+
# @param pattern [String] the pattern to match (without colon)
|
|
93
|
+
# @param case_sensitive [Boolean] whether to match case-sensitively
|
|
94
|
+
# @param require_start [Boolean] whether pattern must be at start of line
|
|
95
|
+
# @return [Boolean] true if the line matches
|
|
96
|
+
def matches_pattern?(line, pattern, case_sensitive, require_start)
|
|
97
|
+
# Build regex for pattern followed by colon
|
|
98
|
+
escaped_pattern = Regexp.escape(pattern)
|
|
99
|
+
|
|
100
|
+
regex_str = if require_start
|
|
101
|
+
# Match at start of line after optional whitespace
|
|
102
|
+
"^\\s*#{escaped_pattern}:"
|
|
103
|
+
else
|
|
104
|
+
# Match anywhere in line (after start or whitespace)
|
|
105
|
+
"(?:^|\\s)#{escaped_pattern}:"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
flags = case_sensitive ? nil : Regexp::IGNORECASE
|
|
109
|
+
regex = Regexp.new(regex_str, flags)
|
|
110
|
+
|
|
111
|
+
line.match?(regex)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @return [Hash] configured patterns mapping informal -> YARD tag
|
|
115
|
+
def config_patterns
|
|
116
|
+
config_or_default('Patterns')
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# @return [Boolean] whether matching should be case-sensitive
|
|
120
|
+
def config_case_sensitive
|
|
121
|
+
config_or_default('CaseSensitive')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @return [Boolean] whether patterns must appear at start of line
|
|
125
|
+
def config_require_start_of_line
|
|
126
|
+
config_or_default('RequireStartOfLine')
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
# InformalNotation validator
|
|
8
|
+
#
|
|
9
|
+
# Detects informal notation patterns in YARD documentation comments
|
|
10
|
+
# and suggests proper YARD tags. For example, "Note:" should use @note,
|
|
11
|
+
# "TODO:" should use @todo, etc.
|
|
12
|
+
#
|
|
13
|
+
# ## Configuration
|
|
14
|
+
#
|
|
15
|
+
# To customize which patterns are detected:
|
|
16
|
+
#
|
|
17
|
+
# ```yaml
|
|
18
|
+
# Tags/InformalNotation:
|
|
19
|
+
# Enabled: true
|
|
20
|
+
# CaseSensitive: false
|
|
21
|
+
# RequireStartOfLine: true
|
|
22
|
+
# Patterns:
|
|
23
|
+
# Note: '@note'
|
|
24
|
+
# TODO: '@todo'
|
|
25
|
+
# ```
|
|
26
|
+
#
|
|
27
|
+
# @example Good - Using proper YARD tags
|
|
28
|
+
# # Calculate the sum of values
|
|
29
|
+
# # @note This method is slow for large arrays
|
|
30
|
+
# # @todo Optimize for performance
|
|
31
|
+
# def sum(values)
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# @example Bad - Using informal notation
|
|
35
|
+
# # Calculate the sum of values
|
|
36
|
+
# # Note: This method is slow for large arrays
|
|
37
|
+
# # TODO: Optimize for performance
|
|
38
|
+
# def sum(values)
|
|
39
|
+
# end
|
|
40
|
+
module InformalNotation
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -9,94 +9,81 @@ module Yard
|
|
|
9
9
|
# By invalid we mean, that they are not classes nor any of the allowed defaults or
|
|
10
10
|
# exclusions.
|
|
11
11
|
class Validator < Base
|
|
12
|
+
# Enable in-process execution with all visibility
|
|
13
|
+
in_process visibility: :all
|
|
14
|
+
|
|
12
15
|
# All non-class yard types that are considered valid
|
|
13
16
|
ALLOWED_DEFAULTS = %w[
|
|
14
17
|
false
|
|
15
18
|
true
|
|
16
19
|
nil
|
|
17
20
|
self
|
|
18
|
-
|
|
21
|
+
void
|
|
19
22
|
].freeze
|
|
20
23
|
|
|
21
24
|
private_constant :ALLOWED_DEFAULTS
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
# Execute query for a single object during in-process execution.
|
|
27
|
+
# Checks for invalid type definitions in tags.
|
|
28
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
29
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
30
|
+
# @return [void]
|
|
31
|
+
def in_process_query(object, collector)
|
|
32
|
+
checked_tags = config_or_default('ValidatedTags')
|
|
33
|
+
extra_types = config_or_default('ExtraTypes')
|
|
34
|
+
allowed_types = ALLOWED_DEFAULTS + extra_types
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
28
|
-
# @return [Hash] shell command execution hash results
|
|
29
|
-
def yard_cmd(dir, file_list_path)
|
|
30
|
-
# Write query to a temporary file to avoid shell escaping issues
|
|
31
|
-
squery = Shellwords.escape(query)
|
|
32
|
-
cmd = "cat #{Shellwords.escape(file_list_path)} | xargs yard list --query #{squery} "
|
|
36
|
+
# Sanitize type string (remove type syntax characters)
|
|
37
|
+
sanitize = ->(type) { type.tr('=><>,{} ', '') }
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
# Check for invalid types
|
|
40
|
+
invalid_types = object.docstring.tags
|
|
41
|
+
.select { |tag| checked_tags.include?(tag.tag_name) }
|
|
42
|
+
.flat_map(&:types)
|
|
43
|
+
.compact
|
|
44
|
+
.uniq
|
|
45
|
+
.map(&sanitize)
|
|
46
|
+
.reject { |type| allowed_types.include?(type) }
|
|
47
|
+
.reject { |type| type_defined?(type) }
|
|
48
|
+
.reject { |type| type.include?('#') }
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# @return [String] multiline yard query that we use to find methods with
|
|
46
|
-
# tags with invalid types definitions
|
|
47
|
-
def query
|
|
48
|
-
<<-QUERY
|
|
49
|
-
'
|
|
50
|
-
sanitize = ->(type) do
|
|
51
|
-
type
|
|
52
|
-
.tr('=>', '')
|
|
53
|
-
.tr('<', '')
|
|
54
|
-
.tr('>', '')
|
|
55
|
-
.tr(' ', '')
|
|
56
|
-
.tr(',', '')
|
|
57
|
-
.tr('{', '')
|
|
58
|
-
.tr('}', '')
|
|
59
|
-
end
|
|
50
|
+
return if invalid_types.empty?
|
|
60
51
|
|
|
61
|
-
|
|
62
|
-
.tags
|
|
63
|
-
.select { |tag| #{checked_tags_names}.include?(tag.tag_name) }
|
|
64
|
-
.map(&:types)
|
|
65
|
-
.flatten
|
|
66
|
-
.uniq
|
|
67
|
-
.compact
|
|
68
|
-
.map(&sanitize)
|
|
69
|
-
.reject { |type| #{allowed_types_code}.include?(type) }
|
|
70
|
-
.reject { |type| !(Kernel.const_defined?(type) rescue nil).nil? }
|
|
71
|
-
.reject { |type| type.include?('#') }
|
|
72
|
-
.then { |types| !types.empty? }
|
|
73
|
-
'
|
|
74
|
-
QUERY
|
|
52
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
75
53
|
end
|
|
76
54
|
|
|
77
|
-
|
|
78
|
-
# types definitions
|
|
79
|
-
def checked_tags_names
|
|
80
|
-
validated_tags = config_or_default('ValidatedTags')
|
|
81
|
-
query_array(validated_tags)
|
|
82
|
-
end
|
|
55
|
+
private
|
|
83
56
|
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
57
|
+
# Check if a type is defined in Ruby runtime or YARD registry
|
|
58
|
+
# In in-process mode, parsed classes are in YARD registry but not loaded into Ruby
|
|
59
|
+
# @param type [String] type name to check
|
|
60
|
+
# @return [Boolean] true if type is defined (or at least recognized as a valid type)
|
|
61
|
+
def type_defined?(type)
|
|
62
|
+
# Symbol types like :foo are valid YARD documentation notations
|
|
63
|
+
# They document that a method accepts specific symbol values
|
|
64
|
+
return true if type.start_with?(':')
|
|
90
65
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
66
|
+
# Check Ruby runtime first
|
|
67
|
+
# The shell query uses: !(Kernel.const_defined?(type) rescue nil).nil?
|
|
68
|
+
# This means: if const_defined? returns ANY value (true or false, not nil),
|
|
69
|
+
# the type is considered "recognized" and should not be flagged as invalid.
|
|
70
|
+
# This allows common types like "Boolean" which aren't actual Ruby classes
|
|
71
|
+
# but are still recognized by Ruby as valid constant names to check.
|
|
72
|
+
begin
|
|
73
|
+
const_result = Kernel.const_defined?(type)
|
|
74
|
+
rescue NameError
|
|
75
|
+
# Invalid constant name syntax (e.g., "foo<bar>" or names with special chars)
|
|
76
|
+
# These aren't valid Ruby constants, so we can't check them this way
|
|
77
|
+
const_result = nil
|
|
78
|
+
end
|
|
79
|
+
return true unless const_result.nil?
|
|
80
|
+
|
|
81
|
+
# Check YARD registry (for classes defined in parsed files)
|
|
82
|
+
# This may fail for malformed type strings or registry issues
|
|
83
|
+
!YARD::Registry.resolve(nil, type).nil?
|
|
84
|
+
rescue NameError
|
|
85
|
+
# Type couldn't be resolved - it's not defined
|
|
86
|
+
false
|
|
100
87
|
end
|
|
101
88
|
end
|
|
102
89
|
end
|
|
@@ -13,7 +13,7 @@ module Yard
|
|
|
13
13
|
# file.rb:LINE: ClassName
|
|
14
14
|
# object_type|tag_name
|
|
15
15
|
# @param yard_output [String] raw YARD query results
|
|
16
|
-
# @
|
|
16
|
+
# @option _kwargs [Object] :unused this parameter accepts no options (reserved for future use)
|
|
17
17
|
# @return [Array<Hash>] array with violation details
|
|
18
18
|
def call(yard_output, **_kwargs)
|
|
19
19
|
return [] if yard_output.nil? || yard_output.strip.empty?
|