yard-lint 1.6.1 → 1.7.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 +69 -1
- data/README.md +4 -3
- data/bin/yard-lint +35 -4
- data/lib/yard/lint/config.rb +21 -4
- data/lib/yard/lint/config_loader.rb +31 -6
- data/lib/yard/lint/config_updater.rb +22 -1
- data/lib/yard/lint/config_validator.rb +2 -1
- data/lib/yard/lint/executor/in_process_registry.rb +58 -23
- data/lib/yard/lint/executor/warning_dispatcher.rb +1 -0
- data/lib/yard/lint/git.rb +44 -3
- data/lib/yard/lint/path_grouper.rb +4 -1
- data/lib/yard/lint/results/aggregate.rb +15 -7
- data/lib/yard/lint/stats_calculator.rb +8 -2
- data/lib/yard/lint/templates/default_config.yml +34 -1
- data/lib/yard/lint/templates/strict_config.yml +34 -1
- data/lib/yard/lint/todo_generator.rb +35 -14
- data/lib/yard/lint/validators/base.rb +39 -1
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/validator.rb +17 -2
- data/lib/yard/lint/validators/documentation/empty_comment_line/validator.rb +5 -2
- data/lib/yard/lint/validators/documentation/line_length/validator.rb +1 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +66 -9
- data/lib/yard/lint/validators/documentation/missing_return/validator.rb +3 -3
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment/validator.rb +79 -11
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment.rb +4 -4
- data/lib/yard/lint/validators/documentation/text_substitution/validator.rb +10 -2
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/config.rb +10 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/parser.rb +18 -4
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +38 -4
- data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +6 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +30 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +1 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +43 -4
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +11 -1
- data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +36 -5
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +10 -6
- data/lib/yard/lint/validators/tags/example_style/validator.rb +1 -1
- data/lib/yard/lint/validators/tags/example_syntax/config.rb +6 -1
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +74 -3
- data/lib/yard/lint/validators/tags/forbidden_tags/validator.rb +7 -3
- data/lib/yard/lint/validators/tags/informal_notation/config.rb +6 -1
- data/lib/yard/lint/validators/tags/informal_notation/validator.rb +24 -3
- data/lib/yard/lint/validators/tags/invalid_types/config.rb +7 -1
- data/lib/yard/lint/validators/tags/invalid_types/parser.rb +22 -5
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +24 -10
- data/lib/yard/lint/validators/tags/missing_yield/validator.rb +44 -4
- data/lib/yard/lint/validators/tags/missing_yield.rb +8 -8
- data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +13 -1
- data/lib/yard/lint/validators/tags/option_tags/result.rb +1 -0
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +30 -2
- data/lib/yard/lint/validators/tags/order/parser.rb +12 -5
- data/lib/yard/lint/validators/tags/order/validator.rb +7 -2
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +21 -8
- data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +12 -5
- data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +9 -7
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +8 -2
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +1 -1
- data/lib/yard/lint/validators/warnings/invalid_directive_format/parser.rb +2 -2
- data/lib/yard/lint/validators/warnings/invalid_tag_format/parser.rb +2 -2
- data/lib/yard/lint/validators/warnings/syntax_error/config.rb +22 -0
- data/lib/yard/lint/validators/warnings/syntax_error/parser.rb +28 -0
- data/lib/yard/lint/validators/warnings/syntax_error/result.rb +27 -0
- data/lib/yard/lint/validators/warnings/syntax_error/validator.rb +15 -0
- data/lib/yard/lint/validators/warnings/syntax_error.rb +34 -0
- data/lib/yard/lint/validators/warnings/unknown_directive/parser.rb +2 -2
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/messages_builder.rb +51 -46
- data/lib/yard/lint/validators/warnings/unknown_tag/messages_builder.rb +20 -3
- data/lib/yard/lint/validators/warnings/unknown_tag/parser.rb +2 -2
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +4 -1
- metadata +6 -1
|
@@ -29,12 +29,18 @@ module Yard
|
|
|
29
29
|
|
|
30
30
|
object.docstring.tags.each do |tag|
|
|
31
31
|
next unless tags_to_check.include?(tag.tag_name)
|
|
32
|
-
next unless tag.name && tag.text && !tag.text.strip.empty?
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
# Option tags carry their description on the nested pair tag;
|
|
34
|
+
# the documented name is the option key (e.g. ":mode"), not the
|
|
35
|
+
# hash parameter name
|
|
36
|
+
data = tag_data(tag)
|
|
37
|
+
param_name = data.equal?(tag) ? tag.name : data.name.to_s.sub(/\A:/, '')
|
|
38
|
+
next if param_name.nil? || param_name.empty?
|
|
39
|
+
next unless data.text && !data.text.strip.empty?
|
|
40
|
+
|
|
41
|
+
description = data.text.strip.gsub(/\.$/, '')
|
|
36
42
|
word_count = description.split.length
|
|
37
|
-
type_name =
|
|
43
|
+
type_name = data.types&.first&.gsub(/[<>{}\[\],]/, '')&.strip
|
|
38
44
|
|
|
39
45
|
next if word_count > max_words
|
|
40
46
|
|
|
@@ -46,7 +52,7 @@ module Yard
|
|
|
46
52
|
next unless pattern_type
|
|
47
53
|
|
|
48
54
|
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
49
|
-
collector.puts "#{tag.tag_name}|#{param_name}|#{
|
|
55
|
+
collector.puts "#{tag.tag_name}|#{param_name}|#{data.text.strip}|#{type_name || ''}|#{pattern_type}|#{word_count}"
|
|
50
56
|
end
|
|
51
57
|
end
|
|
52
58
|
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
@@ -68,7 +74,9 @@ module Yard
|
|
|
68
74
|
# rubocop:disable Metrics/AbcSize, Metrics/ParameterLists
|
|
69
75
|
def detect_pattern(param_name, description, type_name, word_count, articles, generic_terms, connectors, low_value_verbs, patterns)
|
|
70
76
|
desc_parts = description.split
|
|
71
|
-
|
|
77
|
+
# Anchored on both ends: only a whole-word article counts, not
|
|
78
|
+
# any word that merely starts with one (authenticated, theme)
|
|
79
|
+
articles_re = /\A(#{articles.map { |a| Regexp.escape(a) }.join('|')})\z/i
|
|
72
80
|
|
|
73
81
|
# ArticleParam pattern
|
|
74
82
|
if patterns['ArticleParam'] && word_count <= 3 && desc_parts.length == 2
|
|
@@ -97,9 +105,14 @@ module Yard
|
|
|
97
105
|
end
|
|
98
106
|
end
|
|
99
107
|
|
|
100
|
-
# ParamToVerb pattern
|
|
108
|
+
# ParamToVerb pattern: "<param> to <low-value verb>" (e.g.
|
|
109
|
+
# "user to update"). The third word must actually be a low-value
|
|
110
|
+
# verb, otherwise meaningful noun phrases like "path to file" are
|
|
111
|
+
# wrongly flagged.
|
|
101
112
|
if patterns['ParamToVerb'] && word_count <= 4 && desc_parts.length == 3
|
|
102
|
-
if desc_parts[0].downcase == param_name.downcase &&
|
|
113
|
+
if desc_parts[0].downcase == param_name.downcase &&
|
|
114
|
+
desc_parts[1].downcase == 'to' &&
|
|
115
|
+
low_value_verbs.include?(desc_parts[2].downcase)
|
|
103
116
|
return 'param_to_verb'
|
|
104
117
|
end
|
|
105
118
|
end
|
|
@@ -39,12 +39,19 @@ module Yard
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
base_hash.delete_if { |_key, value| value == 'valid' }
|
|
42
|
-
separator_data = base_hash.values.map(&:last)
|
|
43
42
|
|
|
44
|
-
Validators::Documentation::UndocumentedMethodArguments::Parser
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
location_parser = Validators::Documentation::UndocumentedMethodArguments::Parser.new
|
|
44
|
+
|
|
45
|
+
# Parse each location together with its own separators so that an
|
|
46
|
+
# unparseable location line drops only its own offense instead of
|
|
47
|
+
# shifting the separators of all offenses that follow it
|
|
48
|
+
base_hash.values.filter_map do |location, separators|
|
|
49
|
+
element = location_parser.call(location).first
|
|
50
|
+
next unless element
|
|
51
|
+
|
|
52
|
+
element[:separators] = separators
|
|
53
|
+
element
|
|
54
|
+
end
|
|
48
55
|
end
|
|
49
56
|
|
|
50
57
|
private
|
|
@@ -21,7 +21,9 @@ module Yard
|
|
|
21
21
|
#
|
|
22
22
|
# @return [void]
|
|
23
23
|
def in_process_query(object, collector)
|
|
24
|
-
|
|
24
|
+
# is_alias? exists only on method objects; on namespace objects
|
|
25
|
+
# YARD's method_missing raises NameError, so guard by type first
|
|
26
|
+
return if object.type == :method && object.is_alias?
|
|
25
27
|
|
|
26
28
|
docstring = object.docstring.all
|
|
27
29
|
return if docstring.nil? || docstring.empty?
|
|
@@ -52,17 +54,17 @@ module Yard
|
|
|
52
54
|
had_blank_line = true
|
|
53
55
|
|
|
54
56
|
lines.each do |line|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if stripped.empty?
|
|
57
|
+
if line.strip.empty?
|
|
58
58
|
had_blank_line = true
|
|
59
59
|
next
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
# A real YARD tag begins at column 0 of the docstring. Indented
|
|
63
|
+
# @-leading lines are tag continuation or @example/code content
|
|
64
|
+
# (e.g. an instance variable like `@result`), not new tag groups.
|
|
65
|
+
tag_name = line[/\A@(\S+)/, 1]
|
|
65
66
|
|
|
67
|
+
if tag_name
|
|
66
68
|
current_group = group_for_tag(tag_name)
|
|
67
69
|
|
|
68
70
|
if previous_group && current_group != previous_group && !had_blank_line
|
|
@@ -33,8 +33,10 @@ module Yard
|
|
|
33
33
|
(start_line...(end_line - 1)).reverse_each do |line_num|
|
|
34
34
|
line = source_lines[line_num].to_s.strip
|
|
35
35
|
|
|
36
|
-
#
|
|
37
|
-
|
|
36
|
+
# A blank line means the comment above is detached from the
|
|
37
|
+
# definition - YARD does not attach it as the docstring, so stop
|
|
38
|
+
# scanning rather than reaching up to an unrelated comment block.
|
|
39
|
+
break if line.empty?
|
|
38
40
|
|
|
39
41
|
# Stop if we hit code (non-comment line)
|
|
40
42
|
break unless line.start_with?('#')
|
|
@@ -43,6 +45,10 @@ module Yard
|
|
|
43
45
|
next unless line.include?('@')
|
|
44
46
|
|
|
45
47
|
checked_tags.each do |tag_name|
|
|
48
|
+
# The @option grammar is always "name [Type] :key", so the name
|
|
49
|
+
# must precede the type - it is not subject to type_first.
|
|
50
|
+
next if style == 'type_first' && tag_name == 'option'
|
|
51
|
+
|
|
46
52
|
if style == 'type_first'
|
|
47
53
|
# Detect: @tag_name word [Type] (violation when type_first is enforced)
|
|
48
54
|
pattern = /@#{tag_name}\s+(\w+)\s+\[([^\]]+)\]/
|
|
@@ -29,7 +29,7 @@ module Yard
|
|
|
29
29
|
# @return [void]
|
|
30
30
|
def in_process_query(object, collector)
|
|
31
31
|
validated_tags = config.validator_config('Tags/TypeSyntax', 'ValidatedTags') ||
|
|
32
|
-
|
|
32
|
+
Config.defaults['ValidatedTags']
|
|
33
33
|
|
|
34
34
|
all_typed_tags(object.docstring, validated_tags).each do |tag|
|
|
35
35
|
next unless tag.types
|
|
@@ -10,9 +10,9 @@ module Yard
|
|
|
10
10
|
# Set of regexps for detecting warnings reported by YARD stats
|
|
11
11
|
self.regexps = {
|
|
12
12
|
general: /^\[warn\]: Invalid directive format/,
|
|
13
|
-
message:
|
|
13
|
+
message: %r{\[warn\]: (.*?) in file},
|
|
14
14
|
location: /in file `(.*)`/,
|
|
15
|
-
line:
|
|
15
|
+
line: %r{near line (\d+)}
|
|
16
16
|
}.freeze
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -10,9 +10,9 @@ module Yard
|
|
|
10
10
|
# Set of regexps for detecting warnings reported by YARD stats
|
|
11
11
|
self.regexps = {
|
|
12
12
|
general: /^\[warn\]: Invalid tag format/,
|
|
13
|
-
message:
|
|
13
|
+
message: %r{\[warn\]: (.*?) in file},
|
|
14
14
|
location: /in file `(.*)`/,
|
|
15
|
-
line:
|
|
15
|
+
line: %r{near line (\d+)}
|
|
16
16
|
}.freeze
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
# Validators for checking YARD warnings
|
|
7
|
+
module Warnings
|
|
8
|
+
# Validator for detecting files YARD could not parse
|
|
9
|
+
module SyntaxError
|
|
10
|
+
# Configuration for SyntaxError validator
|
|
11
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
12
|
+
self.id = :syntax_error
|
|
13
|
+
self.defaults = {
|
|
14
|
+
'Enabled' => true,
|
|
15
|
+
'Severity' => 'error'
|
|
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 Warnings
|
|
7
|
+
module SyntaxError
|
|
8
|
+
# Parser for SyntaxError warnings.
|
|
9
|
+
#
|
|
10
|
+
# YARD reports a parse failure as a single warning line of the form:
|
|
11
|
+
# Syntax error in `path/to/file.rb`:(LINE,COL): <description>
|
|
12
|
+
# (a sibling `ParserSyntaxError:` line and a stack trace are also
|
|
13
|
+
# emitted, but the `general` pattern matches only the first so each
|
|
14
|
+
# failure yields exactly one offense).
|
|
15
|
+
class Parser < ::Yard::Lint::Parsers::OneLineBase
|
|
16
|
+
# Set of regexps for detecting syntax-error warnings reported by YARD
|
|
17
|
+
self.regexps = {
|
|
18
|
+
general: /^\[warn\]: Syntax error in /,
|
|
19
|
+
message: /:\(\d+,\d+\):\s*(.+)$/,
|
|
20
|
+
location: /Syntax error in `(.+?)`/,
|
|
21
|
+
line: /:\((\d+),\d+\):/
|
|
22
|
+
}.freeze
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Warnings
|
|
7
|
+
module SyntaxError
|
|
8
|
+
# Result object for SyntaxError validation
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'error'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'SyntaxError'
|
|
13
|
+
|
|
14
|
+
# Build human-readable message for a SyntaxError offense
|
|
15
|
+
# @param offense [Hash] offense data with :message key
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
base = 'File could not be parsed by YARD and was skipped'
|
|
19
|
+
detail = offense[:message]
|
|
20
|
+
detail && !detail.empty? ? "#{base}: #{detail}" : base
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Warnings
|
|
7
|
+
module SyntaxError
|
|
8
|
+
# Validator for detecting files YARD could not parse (syntax errors)
|
|
9
|
+
class Validator < Base
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
# Validators for checking YARD warnings
|
|
7
|
+
module Warnings
|
|
8
|
+
# SyntaxError validator
|
|
9
|
+
#
|
|
10
|
+
# Flags Ruby files that YARD could not parse because of a syntax error.
|
|
11
|
+
# Such files are silently skipped by YARD - they contribute no objects and
|
|
12
|
+
# therefore no documentation offenses - so without this validator a run
|
|
13
|
+
# could pass (exit 0) over code that does not even parse. This validator
|
|
14
|
+
# surfaces the parser error as an offense (severity `error` by default),
|
|
15
|
+
# making the run exit non-zero. Enabled by default.
|
|
16
|
+
#
|
|
17
|
+
# For example, a file whose method signature is missing its closing
|
|
18
|
+
# parenthesis (`def foo(x` followed by a body and `end`) fails to parse;
|
|
19
|
+
# YARD reports `Syntax error in <file>:(LINE,COL): ...` and this validator
|
|
20
|
+
# turns that into an offense. A file that parses cleanly produces nothing.
|
|
21
|
+
#
|
|
22
|
+
# ## Configuration
|
|
23
|
+
#
|
|
24
|
+
# To disable this validator:
|
|
25
|
+
#
|
|
26
|
+
# Warnings/SyntaxError:
|
|
27
|
+
# Enabled: false
|
|
28
|
+
#
|
|
29
|
+
module SyntaxError
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -10,9 +10,9 @@ module Yard
|
|
|
10
10
|
# Set of regexps for detecting warnings reported by YARD stats
|
|
11
11
|
self.regexps = {
|
|
12
12
|
general: /^\[warn\]: Unknown directive.*@!.*near line/,
|
|
13
|
-
message:
|
|
13
|
+
message: %r{\[warn\]: (.*?) in file},
|
|
14
14
|
location: /in file `(.*)`/,
|
|
15
|
-
line:
|
|
15
|
+
line: %r{near line (\d+)}
|
|
16
16
|
}.freeze
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'did_you_mean'
|
|
4
|
-
require 'shellwords'
|
|
5
4
|
|
|
6
5
|
module Yard
|
|
7
6
|
module Lint
|
|
@@ -52,14 +51,8 @@ module Yard
|
|
|
52
51
|
|
|
53
52
|
line_num = line.to_i
|
|
54
53
|
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
params = parse_parameters_from_source(file, line_num)
|
|
58
|
-
return params unless params.empty?
|
|
59
|
-
|
|
60
|
-
# Fallback: Query YARD list for the method
|
|
61
|
-
# This requires YARD to parse the file first
|
|
62
|
-
fetch_parameters_via_yard(file, line_num)
|
|
54
|
+
# Parse directly from the Ruby source file.
|
|
55
|
+
parse_parameters_from_source(file, line_num)
|
|
63
56
|
rescue StandardError => e
|
|
64
57
|
# If anything goes wrong, just return empty array (no suggestion)
|
|
65
58
|
warn "Failed to fetch parameters: #{e.message}" if ENV['DEBUG']
|
|
@@ -73,8 +66,10 @@ module Yard
|
|
|
73
66
|
def parse_parameters_from_source(file, line)
|
|
74
67
|
return [] unless File.exist?(file)
|
|
75
68
|
|
|
76
|
-
#
|
|
77
|
-
|
|
69
|
+
# The warning is reported at the method's def line, so start
|
|
70
|
+
# there - scanning earlier would pick up an unrelated method
|
|
71
|
+
# defined in the preceding lines.
|
|
72
|
+
start_line = [line, 1].max
|
|
78
73
|
end_line = line + 5
|
|
79
74
|
|
|
80
75
|
# Only read the lines in the relevant range to avoid loading the whole file
|
|
@@ -92,12 +87,15 @@ module Yard
|
|
|
92
87
|
param_lines = []
|
|
93
88
|
|
|
94
89
|
lines.each do |source_line|
|
|
90
|
+
# The method name may include a receiver (def self.foo, def
|
|
91
|
+
# obj.foo) or be an operator, so match anything up to the
|
|
92
|
+
# opening parenthesis rather than a bare \w+.
|
|
95
93
|
# Match single-line method definitions: def method_name(param1, param2)
|
|
96
|
-
if source_line =~ /^\s*def\s+\
|
|
94
|
+
if source_line =~ /^\s*def\s+[^(]+\((.*?)\)/
|
|
97
95
|
params_str = ::Regexp.last_match(1)
|
|
98
96
|
return extract_parameter_names(params_str)
|
|
99
97
|
# Match start of multi-line method definition: def method_name(
|
|
100
|
-
elsif source_line =~ /^\s*def\s+\
|
|
98
|
+
elsif source_line =~ /^\s*def\s+[^(]+\((.*)$/
|
|
101
99
|
in_multiline_def = true
|
|
102
100
|
param_lines << ::Regexp.last_match(1)
|
|
103
101
|
next
|
|
@@ -111,7 +109,7 @@ module Yard
|
|
|
111
109
|
params_str = params_str[/\A(.*?)\)/, 1] || params_str
|
|
112
110
|
return extract_parameter_names(params_str)
|
|
113
111
|
end
|
|
114
|
-
elsif source_line.match?(/^\s*def\s
|
|
112
|
+
elsif source_line.match?(/^\s*def\s+[^(]+$/)
|
|
115
113
|
# Method with no parameters
|
|
116
114
|
return []
|
|
117
115
|
end
|
|
@@ -130,38 +128,42 @@ module Yard
|
|
|
130
128
|
def extract_parameter_names(params_str)
|
|
131
129
|
return [] if params_str.nil? || params_str.strip.empty?
|
|
132
130
|
|
|
133
|
-
params_str
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
#
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
end.reject(&:empty?)
|
|
131
|
+
split_top_level_params(params_str).filter_map do |param|
|
|
132
|
+
# Strip leading splat/block markers (*args, **kwargs, &block).
|
|
133
|
+
name = param.strip.sub(/\A[*&]+/, '')
|
|
134
|
+
# The name is everything up to the first ':' (keyword) or '='
|
|
135
|
+
# (default), so a symbol default like `mode: :fast` or a default
|
|
136
|
+
# containing commas like `list = [1, 2]` is not mangled.
|
|
137
|
+
name = name[/\A[^:=]+/].to_s.strip
|
|
138
|
+
name.empty? ? nil : name
|
|
139
|
+
end
|
|
143
140
|
end
|
|
144
141
|
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
# @param
|
|
148
|
-
# @return [Array<String>]
|
|
149
|
-
def
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
142
|
+
# Splits a parameter string on top-level commas, respecting
|
|
143
|
+
# brackets so defaults like `[1, 2]` or `{a: 1}` stay intact.
|
|
144
|
+
# @param params_str [String] the raw parameter list
|
|
145
|
+
# @return [Array<String>] individual parameter substrings
|
|
146
|
+
def split_top_level_params(params_str)
|
|
147
|
+
parts = []
|
|
148
|
+
current = +''
|
|
149
|
+
depth = 0
|
|
150
|
+
params_str.each_char do |char|
|
|
151
|
+
case char
|
|
152
|
+
when '(', '[', '{' then depth += 1; current << char
|
|
153
|
+
when ')', ']', '}' then depth -= 1; current << char
|
|
154
|
+
when ','
|
|
155
|
+
if depth.zero?
|
|
156
|
+
parts << current
|
|
157
|
+
current = +''
|
|
158
|
+
else
|
|
159
|
+
current << char
|
|
160
|
+
end
|
|
161
|
+
else
|
|
162
|
+
current << char
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
parts << current
|
|
166
|
+
parts
|
|
165
167
|
end
|
|
166
168
|
|
|
167
169
|
# Find the best suggestion using DidYouMean spell checker
|
|
@@ -203,9 +205,12 @@ module Yard
|
|
|
203
205
|
return nil unless best_match
|
|
204
206
|
|
|
205
207
|
param, distance = best_match
|
|
206
|
-
|
|
208
|
+
# Require the edit distance to be strictly less than half the
|
|
209
|
+
# longer length, so short, very different names are not
|
|
210
|
+
# "corrected" to an unrelated parameter.
|
|
211
|
+
max_length = [unknown_param.length, param.length].max
|
|
207
212
|
|
|
208
|
-
distance
|
|
213
|
+
distance < max_length / 2.0 ? param : nil
|
|
209
214
|
end
|
|
210
215
|
|
|
211
216
|
# Calculate Levenshtein distance between two strings
|
|
@@ -58,8 +58,13 @@ module Yard
|
|
|
58
58
|
suggestion = find_suggestion(unknown_tag)
|
|
59
59
|
|
|
60
60
|
if suggestion
|
|
61
|
+
# A directive (e.g. parse, method, attribute) must be written
|
|
62
|
+
# with the @! prefix; only real tags use a plain @. Rendering
|
|
63
|
+
# a directive suggestion as @parse would itself be an unknown
|
|
64
|
+
# tag.
|
|
65
|
+
prefix = directive_only?(suggestion) ? '@!' : '@'
|
|
61
66
|
# Replace just the descriptive part before "in file"
|
|
62
|
-
message.sub(/Unknown tag @\w+/, "Unknown tag @#{unknown_tag} (did you mean '
|
|
67
|
+
message.sub(/Unknown tag @\w+/, "Unknown tag @#{unknown_tag} (did you mean '#{prefix}#{suggestion}'?)")
|
|
63
68
|
else
|
|
64
69
|
message
|
|
65
70
|
end
|
|
@@ -67,6 +72,14 @@ module Yard
|
|
|
67
72
|
|
|
68
73
|
private
|
|
69
74
|
|
|
75
|
+
# Whether the given name is a YARD directive and not also a tag,
|
|
76
|
+
# so it must be referenced with the @! prefix.
|
|
77
|
+
# @param name [String] candidate tag/directive name
|
|
78
|
+
# @return [Boolean]
|
|
79
|
+
def directive_only?(name)
|
|
80
|
+
known_directives.include?(name) && !known_tags.include?(name)
|
|
81
|
+
end
|
|
82
|
+
|
|
70
83
|
# Find the best suggestion using DidYouMean spell checker
|
|
71
84
|
# @param unknown_tag [String] the unknown tag name (without @ prefix)
|
|
72
85
|
# @return [String, nil] suggested tag name or nil
|
|
@@ -104,9 +117,13 @@ module Yard
|
|
|
104
117
|
return nil unless best_match
|
|
105
118
|
|
|
106
119
|
tag, distance = best_match
|
|
107
|
-
|
|
120
|
+
# Require the edit distance to be strictly less than half the
|
|
121
|
+
# longer length, so short, very different names like @foo/@spec
|
|
122
|
+
# are not "corrected" to @todo/@see (which differ by half their
|
|
123
|
+
# characters).
|
|
124
|
+
max_length = [unknown_tag.length, tag.length].max
|
|
108
125
|
|
|
109
|
-
distance
|
|
126
|
+
distance < max_length / 2.0 ? tag : nil
|
|
110
127
|
end
|
|
111
128
|
|
|
112
129
|
# Calculate Levenshtein distance between two strings
|
|
@@ -12,9 +12,9 @@ module Yard
|
|
|
12
12
|
# Set of regexps for detecting warnings reported by YARD stats
|
|
13
13
|
self.regexps = {
|
|
14
14
|
general: /^\[warn\]: Unknown tag.*@.*near line/,
|
|
15
|
-
message:
|
|
15
|
+
message: %r{\[warn\]: (.*?) in file},
|
|
16
16
|
location: /in file `(.*)`/,
|
|
17
|
-
line:
|
|
17
|
+
line: %r{near line (\d+)}
|
|
18
18
|
}.freeze
|
|
19
19
|
end
|
|
20
20
|
end
|
data/lib/yard/lint/version.rb
CHANGED
data/lib/yard/lint.rb
CHANGED
|
@@ -78,7 +78,10 @@ module Yard
|
|
|
78
78
|
# Get changed files from git based on mode
|
|
79
79
|
git_files = case diff[:mode]
|
|
80
80
|
when :ref
|
|
81
|
-
|
|
81
|
+
# When no explicit REF was given, honor the configured
|
|
82
|
+
# DefaultBaseRef before falling back to main/master.
|
|
83
|
+
base_ref = diff[:base_ref] || config&.diff_mode_default_base_ref
|
|
84
|
+
Git.changed_files(base_ref, path)
|
|
82
85
|
when :staged
|
|
83
86
|
Git.staged_files(path)
|
|
84
87
|
when :changed
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yard-lint
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Maciej Mensfeld
|
|
@@ -262,6 +262,11 @@ files:
|
|
|
262
262
|
- lib/yard/lint/validators/warnings/invalid_tag_format/parser.rb
|
|
263
263
|
- lib/yard/lint/validators/warnings/invalid_tag_format/result.rb
|
|
264
264
|
- lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb
|
|
265
|
+
- lib/yard/lint/validators/warnings/syntax_error.rb
|
|
266
|
+
- lib/yard/lint/validators/warnings/syntax_error/config.rb
|
|
267
|
+
- lib/yard/lint/validators/warnings/syntax_error/parser.rb
|
|
268
|
+
- lib/yard/lint/validators/warnings/syntax_error/result.rb
|
|
269
|
+
- lib/yard/lint/validators/warnings/syntax_error/validator.rb
|
|
265
270
|
- lib/yard/lint/validators/warnings/unknown_directive.rb
|
|
266
271
|
- lib/yard/lint/validators/warnings/unknown_directive/config.rb
|
|
267
272
|
- lib/yard/lint/validators/warnings/unknown_directive/parser.rb
|