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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/README.md +256 -1
  4. data/Rakefile +4 -0
  5. data/bin/yard-lint +24 -0
  6. data/lib/yard/lint/config.rb +35 -1
  7. data/lib/yard/lint/config_loader.rb +1 -1
  8. data/lib/yard/lint/result_builder.rb +10 -1
  9. data/lib/yard/lint/validators/base.rb +41 -12
  10. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +7 -6
  11. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +4 -5
  12. data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
  13. data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +95 -5
  14. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +15 -11
  15. data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +4 -5
  16. data/lib/yard/lint/validators/tags/api_tags/validator.rb +4 -5
  17. data/lib/yard/lint/validators/tags/collection_type/config.rb +21 -0
  18. data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +44 -0
  19. data/lib/yard/lint/validators/tags/collection_type/parser.rb +49 -0
  20. data/lib/yard/lint/validators/tags/collection_type/result.rb +25 -0
  21. data/lib/yard/lint/validators/tags/collection_type/validator.rb +73 -0
  22. data/lib/yard/lint/validators/tags/collection_type.rb +14 -0
  23. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +6 -6
  24. data/lib/yard/lint/validators/tags/meaningless_tag/config.rb +22 -0
  25. data/lib/yard/lint/validators/tags/meaningless_tag/messages_builder.rb +28 -0
  26. data/lib/yard/lint/validators/tags/meaningless_tag/parser.rb +53 -0
  27. data/lib/yard/lint/validators/tags/meaningless_tag/result.rb +26 -0
  28. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +84 -0
  29. data/lib/yard/lint/validators/tags/meaningless_tag.rb +15 -0
  30. data/lib/yard/lint/validators/tags/option_tags/validator.rb +4 -5
  31. data/lib/yard/lint/validators/tags/order/validator.rb +4 -5
  32. data/lib/yard/lint/validators/tags/tag_type_position/config.rb +22 -0
  33. data/lib/yard/lint/validators/tags/tag_type_position/messages_builder.rb +38 -0
  34. data/lib/yard/lint/validators/tags/tag_type_position/parser.rb +51 -0
  35. data/lib/yard/lint/validators/tags/tag_type_position/result.rb +25 -0
  36. data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +113 -0
  37. data/lib/yard/lint/validators/tags/tag_type_position.rb +14 -0
  38. data/lib/yard/lint/validators/tags/type_syntax/config.rb +21 -0
  39. data/lib/yard/lint/validators/tags/type_syntax/messages_builder.rb +27 -0
  40. data/lib/yard/lint/validators/tags/type_syntax/parser.rb +54 -0
  41. data/lib/yard/lint/validators/tags/type_syntax/result.rb +25 -0
  42. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +76 -0
  43. data/lib/yard/lint/validators/tags/type_syntax.rb +14 -0
  44. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +4 -5
  45. data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +4 -5
  46. data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +4 -5
  47. data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +4 -5
  48. data/lib/yard/lint/validators/warnings/unknown_parameter_name/parser.rb +4 -1
  49. data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +4 -5
  50. data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +4 -5
  51. data/lib/yard/lint/version.rb +1 -1
  52. data/lib/yard/lint.rb +1 -0
  53. data/misc/logo.png +0 -0
  54. 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 escaped_file_names [String] files for which we want to get the stats
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, escaped_file_names)
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 escaped_file_names [String] files for which we want to get the stats
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, escaped_file_names)
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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ # TypeSyntax validator module
8
+ # Validates YARD type syntax using YARD's TypesExplainer::Parser
9
+ module TypeSyntax
10
+ end
11
+ end
12
+ end
13
+ end
14
+ 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 escaped_file_names [String] files for which we want to run YARD stats
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, escaped_file_names)
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