yard-lint 0.2.1 → 1.1.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 +73 -1
- data/README.md +121 -89
- data/bin/yard-lint +44 -0
- data/lib/yard/lint/config.rb +35 -3
- data/lib/yard/lint/config_generator.rb +191 -0
- data/lib/yard/lint/config_loader.rb +1 -1
- data/lib/yard/lint/result_builder.rb +10 -1
- data/lib/yard/lint/validators/base.rb +77 -12
- data/lib/yard/lint/validators/documentation/markdown_syntax/config.rb +20 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/messages_builder.rb +44 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/parser.rb +53 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/result.rb +25 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +38 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax.rb +37 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +7 -6
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +26 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +4 -5
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +26 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +95 -5
- data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +15 -11
- data/lib/yard/lint/validators/documentation/undocumented_objects.rb +131 -2
- data/lib/yard/lint/validators/documentation/undocumented_options/config.rb +20 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/parser.rb +53 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/result.rb +29 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +38 -0
- data/lib/yard/lint/validators/documentation/undocumented_options.rb +40 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +4 -5
- data/lib/yard/lint/validators/semantic/abstract_methods.rb +31 -1
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/api_tags.rb +34 -1
- data/lib/yard/lint/validators/tags/collection_type/config.rb +22 -0
- data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +73 -0
- data/lib/yard/lint/validators/tags/collection_type/parser.rb +50 -0
- data/lib/yard/lint/validators/tags/collection_type/result.rb +25 -0
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +92 -0
- data/lib/yard/lint/validators/tags/collection_type.rb +50 -0
- data/lib/yard/lint/validators/tags/example_syntax/config.rb +20 -0
- data/lib/yard/lint/validators/tags/example_syntax/messages_builder.rb +28 -0
- data/lib/yard/lint/validators/tags/example_syntax/parser.rb +79 -0
- data/lib/yard/lint/validators/tags/example_syntax/result.rb +42 -0
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +88 -0
- data/lib/yard/lint/validators/tags/example_syntax.rb +42 -0
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +8 -8
- data/lib/yard/lint/validators/tags/invalid_types.rb +25 -1
- data/lib/yard/lint/validators/tags/meaningless_tag/config.rb +22 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/messages_builder.rb +28 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/parser.rb +53 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/result.rb +26 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +82 -0
- data/lib/yard/lint/validators/tags/meaningless_tag.rb +43 -0
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +11 -6
- data/lib/yard/lint/validators/tags/option_tags.rb +26 -1
- data/lib/yard/lint/validators/tags/order/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/order.rb +25 -1
- data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +33 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +61 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/parser.rb +67 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/result.rb +25 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +148 -0
- data/lib/yard/lint/validators/tags/redundant_param_description.rb +168 -0
- data/lib/yard/lint/validators/tags/tag_type_position/config.rb +22 -0
- data/lib/yard/lint/validators/tags/tag_type_position/messages_builder.rb +38 -0
- data/lib/yard/lint/validators/tags/tag_type_position/parser.rb +51 -0
- data/lib/yard/lint/validators/tags/tag_type_position/result.rb +25 -0
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +111 -0
- data/lib/yard/lint/validators/tags/tag_type_position.rb +51 -0
- data/lib/yard/lint/validators/tags/type_syntax/config.rb +21 -0
- data/lib/yard/lint/validators/tags/type_syntax/messages_builder.rb +27 -0
- data/lib/yard/lint/validators/tags/type_syntax/parser.rb +54 -0
- data/lib/yard/lint/validators/tags/type_syntax/result.rb +25 -0
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +76 -0
- data/lib/yard/lint/validators/tags/type_syntax.rb +38 -0
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +26 -1
- data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +26 -1
- data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/invalid_tag_format.rb +25 -1
- data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_directive.rb +26 -1
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/parser.rb +4 -1
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_parameter_name.rb +23 -1
- data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_tag.rb +26 -1
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +1 -0
- data/misc/logo.png +0 -0
- metadata +64 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module CollectionType
|
|
8
|
+
# Validates Hash collection type syntax in YARD tags
|
|
9
|
+
class Validator < Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Runs YARD query to find incorrect Hash<K, V> syntax
|
|
13
|
+
# @param dir [String] directory where YARD database is stored
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
|
+
# @return [Hash] shell command execution results
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
|
+
# Write query to a temporary file to avoid shell escaping issues
|
|
18
|
+
cmd = "cat #{Shellwords.escape(file_list_path)} | xargs yard list --query #{query} "
|
|
19
|
+
|
|
20
|
+
Tempfile.create(['yard_query', '.sh']) do |f|
|
|
21
|
+
f.write("#!/bin/bash\n")
|
|
22
|
+
f.write(cmd)
|
|
23
|
+
f.write("#{shell_arguments} -b #{Shellwords.escape(dir)}\n")
|
|
24
|
+
f.flush
|
|
25
|
+
f.chmod(0o755)
|
|
26
|
+
|
|
27
|
+
shell("bash #{Shellwords.escape(f.path)}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# YARD query that finds incorrect collection syntax based on EnforcedStyle
|
|
32
|
+
# Format output as two lines per violation:
|
|
33
|
+
# Line 1: file.rb:LINE: ClassName#method_name
|
|
34
|
+
# Line 2: tag_name|type_string|detected_style
|
|
35
|
+
# @return [String] YARD query string
|
|
36
|
+
def query
|
|
37
|
+
style = enforced_style
|
|
38
|
+
|
|
39
|
+
<<~QUERY.strip
|
|
40
|
+
'
|
|
41
|
+
docstring
|
|
42
|
+
.tags
|
|
43
|
+
.select { |tag| #{validated_tags_array}.include?(tag.tag_name) }
|
|
44
|
+
.each do |tag|
|
|
45
|
+
next unless tag.types
|
|
46
|
+
|
|
47
|
+
tag.types.each do |type_str|
|
|
48
|
+
detected_style = nil
|
|
49
|
+
|
|
50
|
+
# Check for Hash<...> syntax (angle brackets)
|
|
51
|
+
if type_str =~ /Hash<.*>/
|
|
52
|
+
detected_style = "short"
|
|
53
|
+
# Check for Hash{...} syntax (curly braces)
|
|
54
|
+
elsif type_str =~ /Hash\\{.*\\}/
|
|
55
|
+
detected_style = "long"
|
|
56
|
+
# Check for {...} syntax without Hash prefix
|
|
57
|
+
elsif type_str =~ /^\\{.*\\}$/
|
|
58
|
+
detected_style = "short"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Report violations based on enforced style
|
|
62
|
+
if detected_style && detected_style != "#{style}"
|
|
63
|
+
puts object.file + ":" + object.line.to_s + ": " + object.title
|
|
64
|
+
puts tag.tag_name + "|" + type_str + "|" + detected_style
|
|
65
|
+
break
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
false
|
|
71
|
+
'
|
|
72
|
+
QUERY
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Gets the enforced collection style from configuration
|
|
76
|
+
# @return [String] 'long' or 'short'
|
|
77
|
+
def enforced_style
|
|
78
|
+
config_or_default('EnforcedStyle')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Array of tag names to validate, formatted for YARD query
|
|
82
|
+
# @return [String] Ruby array literal string
|
|
83
|
+
def validated_tags_array
|
|
84
|
+
tags = config_or_default('ValidatedTags')
|
|
85
|
+
"[#{tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
# CollectionType validator
|
|
8
|
+
#
|
|
9
|
+
# Enforces correct YARD collection type syntax for Hash types. YARD uses
|
|
10
|
+
# `Hash{K => V}` syntax with curly braces and hash rockets, not the generic
|
|
11
|
+
# `Hash<K, V>` syntax used in other languages and documentation tools.
|
|
12
|
+
# This validator is enabled by default.
|
|
13
|
+
#
|
|
14
|
+
# ## Why This Matters
|
|
15
|
+
#
|
|
16
|
+
# YARD has specific syntax conventions that differ from other documentation tools.
|
|
17
|
+
# Using the correct syntax ensures that YARD can properly parse and display your
|
|
18
|
+
# type annotations in generated documentation.
|
|
19
|
+
#
|
|
20
|
+
# @example Bad - Generic syntax with angle brackets
|
|
21
|
+
# # @param options [Hash<Symbol, String>] configuration options
|
|
22
|
+
# # @param mapping [Hash<String, Integer>] key to value mapping
|
|
23
|
+
# def configure(options, mapping)
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# @example Good - YARD syntax with curly braces
|
|
27
|
+
# # @param options [Hash{Symbol => String}] configuration options
|
|
28
|
+
# # @param mapping [Hash{String => Integer}] key to value mapping
|
|
29
|
+
# def configure(options, mapping)
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# @example Good - Arrays use angle brackets (correct for YARD)
|
|
33
|
+
# # @param items [Array<String>] list of items
|
|
34
|
+
# # @param numbers [Array<Integer>] list of numbers
|
|
35
|
+
# def process(items, numbers)
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# ## Configuration
|
|
39
|
+
#
|
|
40
|
+
# To disable this validator:
|
|
41
|
+
#
|
|
42
|
+
# Tags/CollectionType:
|
|
43
|
+
# Enabled: false
|
|
44
|
+
#
|
|
45
|
+
module CollectionType
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Configuration for ExampleSyntax validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :example_syntax
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'warning'
|
|
14
|
+
}.freeze
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Builds messages for example syntax offenses
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Build message for example syntax offense
|
|
12
|
+
# @param offense [Hash] offense data with :example_name and :error_message keys
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
example_name = offense[:example_name]
|
|
16
|
+
error_msg = offense[:error_message]
|
|
17
|
+
object_name = offense[:object_name]
|
|
18
|
+
|
|
19
|
+
"Object `#{object_name}` has syntax error in @example " \
|
|
20
|
+
"'#{example_name}': #{error_msg}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Parser for @example syntax validation results
|
|
9
|
+
class Parser < Parsers::Base
|
|
10
|
+
# @param yard_output [String] raw yard output with example syntax issues
|
|
11
|
+
# @return [Array<Hash>] array with example syntax violation details
|
|
12
|
+
def call(yard_output)
|
|
13
|
+
return [] if yard_output.nil? || yard_output.empty?
|
|
14
|
+
|
|
15
|
+
lines = yard_output.split("\n").reject(&:empty?)
|
|
16
|
+
results = []
|
|
17
|
+
|
|
18
|
+
# Output format is variable lines per error:
|
|
19
|
+
# 1. file.rb:10: ClassName#method_name
|
|
20
|
+
# 2. syntax_error
|
|
21
|
+
# 3. Example name
|
|
22
|
+
# 4+. Error message (can be multiple lines)
|
|
23
|
+
# Next error starts with another file.rb:line: pattern
|
|
24
|
+
|
|
25
|
+
i = 0
|
|
26
|
+
while i < lines.length
|
|
27
|
+
location_line = lines[i]
|
|
28
|
+
|
|
29
|
+
# Parse location line: "file.rb:10: ClassName#method_name"
|
|
30
|
+
# File paths typically start with a letter or . or / or ~
|
|
31
|
+
match = location_line.match(%r{^([a-zA-Z./~].+):(\d+): (.+)$})
|
|
32
|
+
unless match
|
|
33
|
+
i += 1
|
|
34
|
+
next
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
file = match[1]
|
|
38
|
+
line = match[2].to_i
|
|
39
|
+
object_name = match[3]
|
|
40
|
+
|
|
41
|
+
# Next line should be status
|
|
42
|
+
i += 1
|
|
43
|
+
status_line = lines[i]
|
|
44
|
+
next unless status_line == 'syntax_error'
|
|
45
|
+
|
|
46
|
+
# Next line is example name
|
|
47
|
+
i += 1
|
|
48
|
+
example_name = lines[i]
|
|
49
|
+
|
|
50
|
+
# Collect all remaining lines until we hit the next location line or end
|
|
51
|
+
error_message_lines = []
|
|
52
|
+
i += 1
|
|
53
|
+
while i < lines.length
|
|
54
|
+
# Check if this line starts a new error (matches file:line: pattern)
|
|
55
|
+
# File paths typically start with a letter or . or / or ~
|
|
56
|
+
break if lines[i].match?(%r{^[a-zA-Z./~].+:\d+: .+$})
|
|
57
|
+
|
|
58
|
+
error_message_lines << lines[i]
|
|
59
|
+
i += 1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
results << {
|
|
63
|
+
name: 'ExampleSyntax',
|
|
64
|
+
object_name: object_name,
|
|
65
|
+
example_name: example_name,
|
|
66
|
+
error_message: error_message_lines.join("\n"),
|
|
67
|
+
location: file,
|
|
68
|
+
line: line
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
results
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Result object for example syntax validation
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'ExampleSyntaxError'
|
|
13
|
+
|
|
14
|
+
# Build human-readable message for example syntax offense
|
|
15
|
+
# @param offense [Hash] offense data with :example_name and :error_message keys
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
MessagesBuilder.call(offense)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Override to build offenses with dynamic names from parsed data
|
|
24
|
+
# @return [Array<Hash>] array of offense hashes
|
|
25
|
+
def build_offenses
|
|
26
|
+
@parsed_data.map do |offense_data|
|
|
27
|
+
{
|
|
28
|
+
severity: configured_severity,
|
|
29
|
+
type: self.class.offense_type,
|
|
30
|
+
name: offense_data[:name] || self.class.offense_name,
|
|
31
|
+
message: build_message(offense_data),
|
|
32
|
+
location: offense_data[:location] || offense_data[:file],
|
|
33
|
+
location_line: offense_data[:line] || offense_data[:location_line] || 0
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Validator to check syntax of code in @example tags
|
|
9
|
+
class Validator < Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Runs yard list query to find objects with invalid syntax in @example tags
|
|
13
|
+
# @param dir [String] dir where the yard db is (or where it should be generated)
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
|
+
# @return [Hash] shell command execution hash results
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
|
+
cmd = <<~CMD
|
|
18
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
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}")
|
|
25
|
+
|
|
26
|
+
shell(cmd)
|
|
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)
|
|
35
|
+
|
|
36
|
+
example_tags.each_with_index do |example, index|
|
|
37
|
+
code = example.text
|
|
38
|
+
next if code.nil? || code.empty?
|
|
39
|
+
|
|
40
|
+
# Clean the code: strip output indicators (#=>) and everything after it
|
|
41
|
+
code_lines = code.split("\\n").map do |line|
|
|
42
|
+
line.sub(/\\s*#\\s*=>.*$/, "")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
cleaned_code = code_lines.join("\\n").strip
|
|
46
|
+
next if cleaned_code.empty?
|
|
47
|
+
|
|
48
|
+
# Check if code looks incomplete (single expression without context)
|
|
49
|
+
# Skip validation for these cases
|
|
50
|
+
lines = cleaned_code.split("\\n").reject { |l| l.strip.empty? || l.strip.start_with?("#") }
|
|
51
|
+
|
|
52
|
+
# Skip if it is a single line that looks like an incomplete expression
|
|
53
|
+
if lines.size == 1
|
|
54
|
+
line = lines.first.strip
|
|
55
|
+
# Skip method calls, variable references, or simple expressions
|
|
56
|
+
# These are likely incomplete snippets showing usage
|
|
57
|
+
if line.match?(/^[a-z_][a-z0-9_]*(\\.| |$)/) || line.match?(/^[A-Z]/) && !line.match?(/^(class|module|def)\\s/)
|
|
58
|
+
next
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Try to parse the code
|
|
63
|
+
begin
|
|
64
|
+
RubyVM::InstructionSequence.compile(cleaned_code)
|
|
65
|
+
rescue SyntaxError => e
|
|
66
|
+
# Report syntax errors
|
|
67
|
+
example_name = example.name || "Example " + (index + 1).to_s
|
|
68
|
+
puts object.file + ":" + object.line.to_s + ": " + object.title
|
|
69
|
+
puts "syntax_error"
|
|
70
|
+
puts example_name
|
|
71
|
+
puts e.message
|
|
72
|
+
rescue => e
|
|
73
|
+
# Other errors (like NameError, ArgumentError) are fine
|
|
74
|
+
# We only check syntax, not semantics
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
false
|
|
80
|
+
'
|
|
81
|
+
QUERY
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
# Tags validators - validate YARD tag quality and consistency
|
|
7
|
+
module Tags
|
|
8
|
+
# ExampleSyntax validator
|
|
9
|
+
#
|
|
10
|
+
# Validates Ruby syntax in `@example` tags using RubyVM::InstructionSequence.compile().
|
|
11
|
+
# This validator ensures that code examples in documentation are syntactically valid.
|
|
12
|
+
# It automatically strips output indicators (`#=>`) and skips incomplete single-line
|
|
13
|
+
# snippets. This validator is enabled by default.
|
|
14
|
+
#
|
|
15
|
+
# @example Bad - Invalid Ruby syntax in example
|
|
16
|
+
# # @example
|
|
17
|
+
# # def broken(
|
|
18
|
+
# # # missing closing
|
|
19
|
+
# def my_method
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @example Good - Valid Ruby syntax in example
|
|
23
|
+
# # @example
|
|
24
|
+
# # def my_method(name)
|
|
25
|
+
# # puts name
|
|
26
|
+
# # end
|
|
27
|
+
# def my_method(name)
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# ## Configuration
|
|
31
|
+
#
|
|
32
|
+
# To disable this validator:
|
|
33
|
+
#
|
|
34
|
+
# Tags/ExampleSyntax:
|
|
35
|
+
# Enabled: false
|
|
36
|
+
#
|
|
37
|
+
module ExampleSyntax
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -24,17 +24,17 @@ module Yard
|
|
|
24
24
|
|
|
25
25
|
# Runs yard list query with proper settings on a given dir and files
|
|
26
26
|
# @param dir [String] dir where the yard db is (or where it should be generated)
|
|
27
|
-
# @param
|
|
27
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
28
28
|
# @return [Hash] shell command execution hash results
|
|
29
|
-
def yard_cmd(dir,
|
|
29
|
+
def yard_cmd(dir, file_list_path)
|
|
30
30
|
# Write query to a temporary file to avoid shell escaping issues
|
|
31
|
-
|
|
31
|
+
squery = Shellwords.escape(query)
|
|
32
|
+
cmd = "cat #{Shellwords.escape(file_list_path)} | xargs yard list --query #{squery} "
|
|
32
33
|
|
|
33
34
|
Tempfile.create(['yard_query', '.sh']) do |f|
|
|
34
35
|
f.write("#!/bin/bash\n")
|
|
35
|
-
f.write(
|
|
36
|
-
f.write("--private --protected -b #{Shellwords.escape(dir)}
|
|
37
|
-
f.write("#{escaped_file_names}\n")
|
|
36
|
+
f.write(cmd)
|
|
37
|
+
f.write("--private --protected -b #{Shellwords.escape(dir)}\n")
|
|
38
38
|
f.flush
|
|
39
39
|
f.chmod(0o755)
|
|
40
40
|
|
|
@@ -77,14 +77,14 @@ module Yard
|
|
|
77
77
|
# @return [String] tags names for which we want to check the invalid tags
|
|
78
78
|
# types definitions
|
|
79
79
|
def checked_tags_names
|
|
80
|
-
validated_tags =
|
|
80
|
+
validated_tags = config_or_default('ValidatedTags')
|
|
81
81
|
query_array(validated_tags)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# @return [String] extra names that we allow for types definitions in a yard
|
|
85
85
|
# query acceptable form
|
|
86
86
|
def allowed_types_code
|
|
87
|
-
extra_types =
|
|
87
|
+
extra_types = config_or_default('ExtraTypes')
|
|
88
88
|
query_array(ALLOWED_DEFAULTS + extra_types)
|
|
89
89
|
end
|
|
90
90
|
|
|
@@ -4,7 +4,31 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Tags
|
|
7
|
-
# InvalidTypes validator
|
|
7
|
+
# InvalidTypes validator
|
|
8
|
+
#
|
|
9
|
+
# Detects invalid or malformed type annotations in YARD tags. This validator
|
|
10
|
+
# checks that type specifications follow YARD's type syntax rules and that
|
|
11
|
+
# type names are valid. This validator is enabled by default.
|
|
12
|
+
#
|
|
13
|
+
# @example Bad - Invalid type syntax
|
|
14
|
+
# # @param name [String | Integer] the name (wrong pipe syntax)
|
|
15
|
+
# # @return [Array[String]] invalid nested syntax
|
|
16
|
+
# def process(name)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Good - Valid type syntax
|
|
20
|
+
# # @param name [String, Integer] the name (comma-separated union)
|
|
21
|
+
# # @return [Array<String>] valid nested syntax
|
|
22
|
+
# def process(name)
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# ## Configuration
|
|
26
|
+
#
|
|
27
|
+
# To disable this validator:
|
|
28
|
+
#
|
|
29
|
+
# Tags/InvalidTypes:
|
|
30
|
+
# Enabled: false
|
|
31
|
+
#
|
|
8
32
|
module InvalidTypes
|
|
9
33
|
end
|
|
10
34
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MeaninglessTag
|
|
8
|
+
# Configuration for MeaninglessTag validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :meaningless_tag
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'warning',
|
|
14
|
+
'CheckedTags' => %w[param option],
|
|
15
|
+
'InvalidObjectTypes' => %w[class module constant]
|
|
16
|
+
}.freeze
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MeaninglessTag
|
|
8
|
+
# Builds human-readable messages for MeaninglessTag violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Formats a meaningless tag violation message
|
|
12
|
+
# @param offense [Hash] offense details with :object_type, :tag_name, :object_name
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
object_type = offense[:object_type]
|
|
16
|
+
tag_name = offense[:tag_name]
|
|
17
|
+
object_name = offense[:object_name]
|
|
18
|
+
|
|
19
|
+
"The @#{tag_name} tag is meaningless on a #{object_type} " \
|
|
20
|
+
"(#{object_name}). This tag only makes sense on methods."
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MeaninglessTag
|
|
8
|
+
# Parser for MeaninglessTag validator output
|
|
9
|
+
# Parses YARD query output that reports meaningless tags on non-methods
|
|
10
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
11
|
+
# Parses YARD output and extracts meaningless tag violations
|
|
12
|
+
# Expected format (two lines per violation):
|
|
13
|
+
# file.rb:LINE: ClassName
|
|
14
|
+
# object_type|tag_name
|
|
15
|
+
# @param yard_output [String] raw YARD query results
|
|
16
|
+
# @param _kwargs [Hash] unused keyword arguments (for compatibility)
|
|
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: ClassName"
|
|
28
|
+
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
29
|
+
next unless location_match
|
|
30
|
+
|
|
31
|
+
# Parse details: "object_type|tag_name"
|
|
32
|
+
details = details_line.split('|', 2)
|
|
33
|
+
next unless details.size == 2
|
|
34
|
+
|
|
35
|
+
object_type, tag_name = details
|
|
36
|
+
|
|
37
|
+
violations << {
|
|
38
|
+
location: location_match[1],
|
|
39
|
+
line: location_match[2].to_i,
|
|
40
|
+
object_name: location_match[3],
|
|
41
|
+
object_type: object_type,
|
|
42
|
+
tag_name: tag_name
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
violations
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
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 MeaninglessTag
|
|
8
|
+
# Result wrapper for MeaninglessTag validator
|
|
9
|
+
# Formats parsed violations into offense objects
|
|
10
|
+
class Result < Results::Base
|
|
11
|
+
self.default_severity = 'warning'
|
|
12
|
+
self.offense_type = 'class'
|
|
13
|
+
self.offense_name = 'MeaninglessTag'
|
|
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
|