yard-lint 0.2.0 → 1.0.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 +36 -1
- data/README.md +256 -1
- data/Rakefile +4 -0
- data/bin/yard-lint +24 -0
- data/lib/yard/lint/config.rb +35 -1
- 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 +41 -12
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +7 -6
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +4 -5
- 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/semantic/abstract_methods/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/collection_type/config.rb +21 -0
- data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +44 -0
- data/lib/yard/lint/validators/tags/collection_type/parser.rb +49 -0
- data/lib/yard/lint/validators/tags/collection_type/result.rb +25 -0
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +73 -0
- data/lib/yard/lint/validators/tags/collection_type.rb +14 -0
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +6 -6
- 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 +84 -0
- data/lib/yard/lint/validators/tags/meaningless_tag.rb +15 -0
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/order/validator.rb +4 -5
- 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 +113 -0
- data/lib/yard/lint/validators/tags/tag_type_position.rb +14 -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 +14 -0
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +4 -5
- 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_tag/validator.rb +4 -5
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +1 -0
- data/misc/logo.png +0 -0
- metadata +27 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MeaninglessTag
|
|
8
|
+
# Validates that @param/@option tags only appear on methods
|
|
9
|
+
class Validator < Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Runs YARD query to find @param/@option tags on non-methods
|
|
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("--private --protected #{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 method-only tags on non-method objects
|
|
32
|
+
# Format output as two lines per violation:
|
|
33
|
+
# Line 1: file.rb:LINE: ClassName
|
|
34
|
+
# Line 2: object_type|tag_name
|
|
35
|
+
# @return [String] YARD query string
|
|
36
|
+
def query
|
|
37
|
+
<<~QUERY.strip
|
|
38
|
+
'
|
|
39
|
+
object_type = object.type.to_s
|
|
40
|
+
|
|
41
|
+
if #{invalid_object_types_array}.include?(object_type)
|
|
42
|
+
docstring.tags.each do |tag|
|
|
43
|
+
if #{checked_tags_array}.include?(tag.tag_name)
|
|
44
|
+
puts object.file + ":" + object.line.to_s + ": " + object.title
|
|
45
|
+
puts object_type + "|" + tag.tag_name
|
|
46
|
+
break
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
false
|
|
52
|
+
'
|
|
53
|
+
QUERY
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Array of tag names to check, formatted for YARD query
|
|
57
|
+
# @return [String] Ruby array literal string
|
|
58
|
+
def checked_tags_array
|
|
59
|
+
"[#{checked_tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Array of invalid object types, formatted for YARD query
|
|
63
|
+
# @return [String] Ruby array literal string
|
|
64
|
+
def invalid_object_types_array
|
|
65
|
+
"[#{invalid_object_types.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @return [Array<String>] tags that should only appear on methods
|
|
69
|
+
def checked_tags
|
|
70
|
+
config.validator_config('Tags/MeaninglessTag', 'CheckedTags') || %w[param option]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [Array<String>] object types that shouldn't have method-only tags
|
|
74
|
+
def invalid_object_types
|
|
75
|
+
config.validator_config('Tags/MeaninglessTag', 'InvalidObjectTypes') || %w[
|
|
76
|
+
class module constant
|
|
77
|
+
]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
# MeaninglessTag validator module
|
|
8
|
+
# Detects @param and @option tags on classes, modules, or constants
|
|
9
|
+
# (these tags only make sense on methods)
|
|
10
|
+
module MeaninglessTag
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -11,15 +11,14 @@ module Yard
|
|
|
11
11
|
|
|
12
12
|
# Runs yard list query to find methods with options parameter but missing @option tags
|
|
13
13
|
# @param dir [String] dir where the yard db is (or where it should be generated)
|
|
14
|
-
# @param
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
15
|
# @return [Hash] shell command execution hash results
|
|
16
|
-
def yard_cmd(dir,
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
17
|
cmd = <<~CMD
|
|
18
|
-
yard list \
|
|
18
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
19
19
|
--private \
|
|
20
20
|
--protected \
|
|
21
|
-
-b #{Shellwords.escape(dir)}
|
|
22
|
-
#{escaped_file_names}
|
|
21
|
+
-b #{Shellwords.escape(dir)}
|
|
23
22
|
CMD
|
|
24
23
|
cmd = cmd.tr("\n", ' ')
|
|
25
24
|
cmd = cmd.gsub('yard list', "yard list --query #{query}")
|
|
@@ -12,16 +12,15 @@ module Yard
|
|
|
12
12
|
|
|
13
13
|
# Runs yard list query with proper settings on a given dir and files
|
|
14
14
|
# @param dir [String] dir where the yard db is (or where it should be generated)
|
|
15
|
-
# @param
|
|
15
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
16
16
|
# @return [Hash] shell command execution hash results
|
|
17
|
-
def yard_cmd(dir,
|
|
17
|
+
def yard_cmd(dir, file_list_path)
|
|
18
18
|
cmd = <<~CMD
|
|
19
|
-
yard list \
|
|
19
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
20
20
|
--private \
|
|
21
21
|
--protected \
|
|
22
22
|
--query #{query} \
|
|
23
|
-
-b #{Shellwords.escape(dir)}
|
|
24
|
-
#{escaped_file_names}
|
|
23
|
+
-b #{Shellwords.escape(dir)}
|
|
25
24
|
CMD
|
|
26
25
|
|
|
27
26
|
result = shell(cmd)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagTypePosition
|
|
8
|
+
# Configuration for TagTypePosition validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :tag_type_position
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'convention',
|
|
14
|
+
'CheckedTags' => %w[param option],
|
|
15
|
+
'EnforcedStyle' => 'type_after_name' # 'type_after_name' (standard) or 'type_first'
|
|
16
|
+
}.freeze
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagTypePosition
|
|
8
|
+
# Builds human-readable messages for TagTypePosition violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Formats a violation message
|
|
12
|
+
# @param offense [Hash] the offense details
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
tag_name = offense[:tag_name]
|
|
16
|
+
param_name = offense[:param_name]
|
|
17
|
+
type_info = offense[:type_info]
|
|
18
|
+
detected_style = offense[:detected_style]
|
|
19
|
+
|
|
20
|
+
if detected_style == 'type_after_name'
|
|
21
|
+
# Enforcing type_first, but found type_after_name
|
|
22
|
+
"Type should appear before parameter name in @#{tag_name} tag. " \
|
|
23
|
+
"Use '@#{tag_name} [#{type_info}] #{param_name}' instead of " \
|
|
24
|
+
"'@#{tag_name} #{param_name} [#{type_info}]'."
|
|
25
|
+
else
|
|
26
|
+
# Enforcing type_after_name, but found type_first
|
|
27
|
+
"Type should appear after parameter name in @#{tag_name} tag. " \
|
|
28
|
+
"Use '@#{tag_name} #{param_name} [#{type_info}]' instead of " \
|
|
29
|
+
"'@#{tag_name} [#{type_info}] #{param_name}'."
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagTypePosition
|
|
8
|
+
# Parses YARD output for TagTypePosition violations
|
|
9
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
10
|
+
# Parses YARD query output into structured violation data
|
|
11
|
+
# @param yard_output [String] raw output from YARD query
|
|
12
|
+
# @param _kwargs [Hash] additional keyword arguments (unused)
|
|
13
|
+
# @return [Array<Hash>] array of violation hashes
|
|
14
|
+
def call(yard_output, **_kwargs)
|
|
15
|
+
return [] if yard_output.nil? || yard_output.strip.empty?
|
|
16
|
+
|
|
17
|
+
lines = yard_output.split("\n").map(&:strip).reject(&:empty?)
|
|
18
|
+
violations = []
|
|
19
|
+
|
|
20
|
+
lines.each_slice(2) do |location_line, details_line|
|
|
21
|
+
next unless location_line && details_line
|
|
22
|
+
|
|
23
|
+
# Parse location: "file.rb:10: ClassName#method_name"
|
|
24
|
+
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
25
|
+
next unless location_match
|
|
26
|
+
|
|
27
|
+
# Parse details: "tag_name|param_name|type_info|detected_style"
|
|
28
|
+
details = details_line.split('|', 4)
|
|
29
|
+
next unless details.size >= 3
|
|
30
|
+
|
|
31
|
+
tag_name, param_name, type_info, detected_style = details
|
|
32
|
+
|
|
33
|
+
violations << {
|
|
34
|
+
location: location_match[1],
|
|
35
|
+
line: location_match[2].to_i,
|
|
36
|
+
object_name: location_match[3],
|
|
37
|
+
tag_name: tag_name,
|
|
38
|
+
param_name: param_name,
|
|
39
|
+
type_info: type_info,
|
|
40
|
+
detected_style: detected_style
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
violations
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagTypePosition
|
|
8
|
+
# Result wrapper for TagTypePosition violations
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'convention'
|
|
11
|
+
self.offense_type = 'style'
|
|
12
|
+
self.offense_name = 'TagTypePosition'
|
|
13
|
+
|
|
14
|
+
# Builds a human-readable message for a violation
|
|
15
|
+
# @param offense [Hash] the offense details
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
MessagesBuilder.call(offense)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagTypePosition
|
|
8
|
+
# Validates type annotation position in @param and @option tags
|
|
9
|
+
# YARD standard (type_after_name): @param name [String] description
|
|
10
|
+
# Alternative (type_first): @param name [String] description
|
|
11
|
+
#
|
|
12
|
+
# Note: @return tags are not checked as they don't have parameter names
|
|
13
|
+
class Validator < Base
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# Runs YARD query to check type position in tags
|
|
17
|
+
# @param dir [String] directory where YARD database is stored
|
|
18
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
19
|
+
# @return [Hash] shell command execution results
|
|
20
|
+
def yard_cmd(dir, file_list_path)
|
|
21
|
+
# Write query to a temporary file to avoid shell escaping issues
|
|
22
|
+
cmd = "cat #{Shellwords.escape(file_list_path)} | xargs yard list --query #{query} "
|
|
23
|
+
|
|
24
|
+
Tempfile.create(['yard_query', '.sh']) do |f|
|
|
25
|
+
f.write("#!/bin/bash\n")
|
|
26
|
+
f.write(cmd)
|
|
27
|
+
f.write("#{shell_arguments} -b #{Shellwords.escape(dir)}\n")
|
|
28
|
+
f.flush
|
|
29
|
+
f.chmod(0o755)
|
|
30
|
+
|
|
31
|
+
shell("bash #{Shellwords.escape(f.path)}")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# YARD query that checks source code directly instead of docstring.all
|
|
36
|
+
# Detects patterns based on configured style
|
|
37
|
+
# @return [String] YARD query string
|
|
38
|
+
def query
|
|
39
|
+
<<~QUERY.strip
|
|
40
|
+
'
|
|
41
|
+
require "ripper"
|
|
42
|
+
|
|
43
|
+
checked_tags = #{checked_tags_array}
|
|
44
|
+
enforced_style = "#{enforced_style}"
|
|
45
|
+
|
|
46
|
+
# Read the source file and find comment lines for this object
|
|
47
|
+
return false unless object.file && File.exist?(object.file)
|
|
48
|
+
|
|
49
|
+
source_lines = File.readlines(object.file)
|
|
50
|
+
start_line = [object.line - 50, 0].max
|
|
51
|
+
end_line = [object.line, source_lines.length - 1].min
|
|
52
|
+
|
|
53
|
+
# Look for comments before the object definition
|
|
54
|
+
# Start just before the object line and scan backward
|
|
55
|
+
(start_line...(end_line - 1)).reverse_each do |line_num|
|
|
56
|
+
line = source_lines[line_num].to_s.strip
|
|
57
|
+
|
|
58
|
+
# Skip empty lines
|
|
59
|
+
next if line.empty?
|
|
60
|
+
|
|
61
|
+
# Stop if we hit code (non-comment line)
|
|
62
|
+
break unless line.start_with?("#")
|
|
63
|
+
|
|
64
|
+
# Skip comment-only lines without tags
|
|
65
|
+
next unless line.include?("@")
|
|
66
|
+
|
|
67
|
+
checked_tags.each do |tag_name|
|
|
68
|
+
if enforced_style == "type_first"
|
|
69
|
+
# Detect: @tag_name word [Type] (violation when type_first is enforced)
|
|
70
|
+
pattern = /@\#{tag_name}\\s+(\\w+)\\s+\\[([^\\]]+)\\]/
|
|
71
|
+
if line =~ pattern
|
|
72
|
+
param_name = $1
|
|
73
|
+
type_info = $2
|
|
74
|
+
puts object.file + ":" + (line_num + 1).to_s + ": " + object.title
|
|
75
|
+
puts tag_name + "|" + param_name + "|" + type_info + "|type_after_name"
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
# Detect: @tag_name [Type] word (violation when type_after_name is enforced)
|
|
79
|
+
pattern = /@\#{tag_name}\\s+\\[([^\\]]+)\\]\\s+(\\w+)/
|
|
80
|
+
if line =~ pattern
|
|
81
|
+
type_info = $1
|
|
82
|
+
param_name = $2
|
|
83
|
+
puts object.file + ":" + (line_num + 1).to_s + ": " + object.title
|
|
84
|
+
puts tag_name + "|" + param_name + "|" + type_info + "|type_first"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
false
|
|
91
|
+
'
|
|
92
|
+
QUERY
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [String] the enforced style ('type_after_name' (standard) or 'type_first')
|
|
96
|
+
def enforced_style
|
|
97
|
+
config.validator_config('Tags/TagTypePosition', 'EnforcedStyle') || 'type_after_name'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Array of tag names to check, formatted for YARD query
|
|
101
|
+
# @return [String] Ruby array literal string
|
|
102
|
+
def checked_tags_array
|
|
103
|
+
tags = config.validator_config(
|
|
104
|
+
'Tags/TagTypePosition', 'CheckedTags'
|
|
105
|
+
) || %w[param option]
|
|
106
|
+
"[#{tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
# TagTypePosition validator module
|
|
8
|
+
# Detects type annotations in incorrect position (after param name instead of before)
|
|
9
|
+
module TagTypePosition
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TypeSyntax
|
|
8
|
+
# Configuration for TypeSyntax validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :type_syntax
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'warning',
|
|
14
|
+
'ValidatedTags' => %w[param option return yieldreturn]
|
|
15
|
+
}.freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TypeSyntax
|
|
8
|
+
# Builds human-readable messages for type syntax violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Formats a type syntax violation message
|
|
12
|
+
# @param offense [Hash] offense details with tag_name, type_string, error_message
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
tag = offense[:tag_name]
|
|
16
|
+
type = offense[:type_string]
|
|
17
|
+
error = offense[:error_message]
|
|
18
|
+
|
|
19
|
+
"Invalid type syntax in @#{tag} tag: '#{type}' (#{error})"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TypeSyntax
|
|
8
|
+
# Parser for TypeSyntax validator output
|
|
9
|
+
# Parses YARD query output that reports type syntax errors
|
|
10
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
11
|
+
# Parses YARD output and extracts type syntax violations
|
|
12
|
+
# Expected format (two lines per violation):
|
|
13
|
+
# file.rb:LINE: ClassName#method_name
|
|
14
|
+
# tag_name|type_string|error_message
|
|
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#method_name"
|
|
28
|
+
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
29
|
+
next unless location_match
|
|
30
|
+
|
|
31
|
+
# Parse details: "tag_name|type_string|error_message"
|
|
32
|
+
details = details_line.split('|', 3)
|
|
33
|
+
next unless details.size == 3
|
|
34
|
+
|
|
35
|
+
tag_name, type_string, error_message = details
|
|
36
|
+
|
|
37
|
+
violations << {
|
|
38
|
+
location: location_match[1],
|
|
39
|
+
line: location_match[2].to_i,
|
|
40
|
+
method_name: location_match[3],
|
|
41
|
+
tag_name: tag_name,
|
|
42
|
+
type_string: type_string,
|
|
43
|
+
error_message: error_message
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
violations
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TypeSyntax
|
|
8
|
+
# Result wrapper for TypeSyntax validator
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'method'
|
|
12
|
+
self.offense_name = 'InvalidTypeSyntax'
|
|
13
|
+
|
|
14
|
+
# Builds a human-readable message for the offense
|
|
15
|
+
# @param offense [Hash] offense details
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
MessagesBuilder.call(offense)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TypeSyntax
|
|
8
|
+
# Runs YARD to validate type syntax using TypesExplainer::Parser
|
|
9
|
+
class Validator < Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Runs YARD query to validate type syntax on given files
|
|
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 validates type syntax for each tag
|
|
32
|
+
# Format output as two lines per violation:
|
|
33
|
+
# Line 1: file.rb:LINE: ClassName#method_name
|
|
34
|
+
# Line 2: tag_name|type_string|error_message
|
|
35
|
+
# @return [String] YARD query string
|
|
36
|
+
def query
|
|
37
|
+
<<~QUERY.strip
|
|
38
|
+
'
|
|
39
|
+
require "yard"
|
|
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
|
+
begin
|
|
49
|
+
YARD::Tags::TypesExplainer::Parser.parse(type_str)
|
|
50
|
+
rescue SyntaxError => e
|
|
51
|
+
puts object.file + ":" + object.line.to_s + ": " + object.title
|
|
52
|
+
puts tag.tag_name + "|" + type_str + "|" + e.message
|
|
53
|
+
break
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
false
|
|
59
|
+
'
|
|
60
|
+
QUERY
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Array of tag names to validate, formatted for YARD query
|
|
64
|
+
# @return [String] Ruby array literal string
|
|
65
|
+
def validated_tags_array
|
|
66
|
+
tags = config.validator_config('Tags/TypeSyntax', 'ValidatedTags') || %w[
|
|
67
|
+
param option return yieldreturn
|
|
68
|
+
]
|
|
69
|
+
"[#{tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -11,15 +11,14 @@ module Yard
|
|
|
11
11
|
|
|
12
12
|
# Runs YARD stats command with proper settings on a given dir and files
|
|
13
13
|
# @param dir [String] dir where we should generate the temp docs
|
|
14
|
-
# @param
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
15
|
# @return [Hash] shell command execution hash results
|
|
16
|
-
def yard_cmd(dir,
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
17
|
cmd = <<~CMD
|
|
18
|
-
yard stats \
|
|
18
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard stats \
|
|
19
19
|
#{shell_arguments} \
|
|
20
20
|
--compact \
|
|
21
|
-
-b #{Shellwords.escape(dir)}
|
|
22
|
-
#{escaped_file_names}
|
|
21
|
+
-b #{Shellwords.escape(dir)}
|
|
23
22
|
CMD
|
|
24
23
|
cmd = cmd.tr("\n", ' ')
|
|
25
24
|
|