Skip to content

Commit 47661c0

Browse files
authored
Merge pull request #13 from ksss/method-argument
Check undocumented parameter
2 parents bd3951e + c11aa6c commit 47661c0

File tree

5 files changed

+153
-13
lines changed

5 files changed

+153
-13
lines changed

Rakefile

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace :smoke do
2222
{ name: "meaningless_tag" }
2323
],
2424
'YARD/MismatchName' => [
25-
{ name: "mismatch_name" }
25+
{ name: "mismatch_name", correct: true }
2626
],
2727
'YARD/CollectionStyle' => [
2828
{ name: "collection_style", style: "long", correct: true },
@@ -60,10 +60,8 @@ namespace :smoke do
6060
if content[:correct]
6161
corrected_path = "smoke/generated/#{with_style_name}_correct.rb"
6262
puts "Running #{corrected_path}"
63-
actual = `#{cmd} #{corrected_path}`
64-
unless JSON.parse(actual)["summary"]["offense_count"] == 0
65-
errors << "Unexpected autocorrected output #{corrected_path}"
66-
end
63+
system("#{cmd} --autocorrect #{corrected_path}")
64+
sh("git diff --exit-code #{corrected_path}")
6765
end
6866
end
6967

@@ -85,7 +83,7 @@ namespace :smoke do
8583
if content[:correct]
8684
correct_path = "smoke/generated/#{with_style_name}_correct.rb"
8785
IO.copy_stream(rb_path, correct_path)
88-
sh("#{cmd} --autocorrect #{correct_path}")
86+
system("#{cmd} --autocorrect #{correct_path}")
8987
end
9088
sh("#{cmd} #{rb_path} | jq > #{json_path}")
9189
end

lib/rubocop/cop/yard/mismatch_name.rb

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class MismatchName < Base
2020
include YARD::Helper
2121
include RangeHelp
2222
include DocumentationComment
23+
extend AutoCorrector
2324

2425
def on_def(node)
2526
return unless node.arguments?
@@ -28,7 +29,12 @@ def on_def(node)
2829
return false unless preceding_comment?(node, preceding_lines.last)
2930

3031
yard_docstring = preceding_lines.map { |line| line.text.gsub(/\A#\s*/, '') }.join("\n")
31-
docstring = ::YARD::DocstringParser.new.parse(yard_docstring)
32+
docstring = begin
33+
::YARD::DocstringParser.new.parse(yard_docstring)
34+
rescue
35+
return false
36+
end
37+
3238
return false if include_overload_tag?(docstring)
3339

3440
each_tags_by_docstring(['param', 'option'], docstring) do |tags|
@@ -56,16 +62,54 @@ def on_def(node)
5662
parse_type(types.join(', '))
5763
rescue SyntaxError
5864
next
59-
end
65+
end if types
6066

61-
add_offense_to_tag(comment, tag)
67+
add_offense_to_tag(node, comment, tag)
68+
end
69+
end
70+
71+
# Documentation only or just `@return` is a common form of documentation.
72+
# The subsequent features will be limited to cases where both `@param` and `@option` are present.
73+
unless docstring.tags.find { |tag| (tag.tag_name == 'param' && !tag.instance_of?(::YARD::Tags::RefTagList)) || tag.tag_name == 'option' }
74+
return false
75+
end
76+
node.arguments.each do |argument|
77+
next if argument.type == :blockarg
78+
next if argument.name.nil?
79+
80+
found = docstring.tags.find do |tag|
81+
next unless tag.tag_name == 'param' || tag.tag_name == 'option'
82+
tag.name&.to_sym == argument.name
83+
end
84+
85+
unless found
86+
comment = preceding_lines.last
87+
return if part_of_ignored_node?(comment)
88+
add_offense(comment, message: "This method has argument `#{argument.name}`, But not documented") do |corrector|
89+
corrector.replace(
90+
comment.source_range.end,
91+
"#{comment.source_range.end.join(node.source_range.begin).source}# #{tag_prototype(argument)}"
92+
)
93+
end
6294
end
6395
end
6496
end
6597
alias on_defs on_def
6698

6799
private
68100

101+
# @param [RuboCop::AST::ArgNode] argument
102+
def tag_prototype(argument)
103+
case argument.type
104+
when :kwrestarg
105+
"@param [Hash{Symbol => Object}] #{argument.name}"
106+
when :restarg
107+
"@param [Array<Object>] #{argument.name}"
108+
else
109+
"@param [Object] #{argument.name}"
110+
end
111+
end
112+
69113
def each_tags_by_docstring(tag_names, docstring)
70114
tag_names.each do |tag_name|
71115
yield docstring.tags.select { |tag| tag.tag_name == tag_name }
@@ -80,13 +124,21 @@ def find_by_tag(preceding_lines, tag, i)
80124
end
81125
end
82126

83-
def add_offense_to_tag(comment, tag)
127+
def add_offense_to_tag(node, comment, tag)
84128
tag_name_regexp = Regexp.new("\\b#{Regexp.escape(tag.name)}\\b")
85129
start_column = comment.source.index(tag_name_regexp)
86130
offense_start = comment.location.column + start_column
87131
offense_end = offense_start + tag.name.length - 1
88132
range = source_range(processed_source.buffer, comment.location.line, offense_start..offense_end)
89-
add_offense(range, message: "`#{tag.name}` is not found in method arguments")
133+
argument_names = node.arguments.map(&:name).compact
134+
argument_name =
135+
if argument_names.empty?
136+
''
137+
else
138+
" of [#{argument_names.join(', ')}]"
139+
end
140+
add_offense(range, message: "`#{tag.name}` is not found in method arguments#{argument_name}")
141+
ignore_node(comment)
90142
end
91143

92144
def include_overload_tag?(docstring)

smoke/generated/mismatch_name.json

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
},
6161
{
6262
"severity": "convention",
63-
"message": "`opt` is not found in method arguments",
63+
"message": "`opt` is not found in method arguments of [bar, opts]",
6464
"cop_name": "YARD/MismatchName",
6565
"corrected": false,
6666
"correctable": false,
@@ -105,12 +105,28 @@
105105
"line": 9,
106106
"column": 3
107107
}
108+
},
109+
{
110+
"severity": "convention",
111+
"message": "This method has argument `opts`, But not documented",
112+
"cop_name": "YARD/MismatchName",
113+
"corrected": false,
114+
"correctable": true,
115+
"location": {
116+
"start_line": 17,
117+
"start_column": 3,
118+
"last_line": 17,
119+
"last_column": 27,
120+
"length": 25,
121+
"line": 17,
122+
"column": 3
123+
}
108124
}
109125
]
110126
}
111127
],
112128
"summary": {
113-
"offense_count": 6,
129+
"offense_count": 7,
114130
"target_file_count": 1,
115131
"inspected_file_count": 1
116132
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
class Foo
2+
# @param [void] bar
3+
# @param
4+
# @param aaa
5+
# @param [void]
6+
# @option opt aaa [void]
7+
# @option opts aaa
8+
# @option opts aaa [void]
9+
# @param [void]
10+
# @param (see #route_docs)
11+
# @param [Hash<Symbol=>Object] config Hash containing optional configuration
12+
# @return [void]
13+
# @return [void] fooo
14+
def foo(bar, opts = {})
15+
end
16+
17+
# @param [String] strings
18+
# @param [Object] opts
19+
# @param [Object] a
20+
# @param [Array<Object>] rest
21+
# @param [Hash{Symbol => Object}] kw
22+
def bar(strings, opts = {}, a = nil, *rest, **kw)
23+
end
24+
25+
# @return [void]
26+
def return_only(arg)
27+
end
28+
29+
# this is a doc
30+
def doc_only(arg)
31+
end
32+
33+
# @param (see #other)
34+
def ref_only(arg)
35+
end
36+
37+
# @param [String] arg
38+
def rest_block(arg, *, **, &block)
39+
end
40+
41+
# @param [String] arg
42+
def delegate(arg, ...)
43+
end
44+
45+
def empty_doc(arg)
46+
end
47+
end

smoke/mismatch_name.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,31 @@ class Foo
1313
# @return [void] fooo
1414
def foo(bar, opts = {})
1515
end
16+
17+
# @param [String] strings
18+
def bar(strings, opts = {}, a = nil, *rest, **kw)
19+
end
20+
21+
# @return [void]
22+
def return_only(arg)
23+
end
24+
25+
# this is a doc
26+
def doc_only(arg)
27+
end
28+
29+
# @param (see #other)
30+
def ref_only(arg)
31+
end
32+
33+
# @param [String] arg
34+
def rest_block(arg, *, **, &block)
35+
end
36+
37+
# @param [String] arg
38+
def delegate(arg, ...)
39+
end
40+
41+
def empty_doc(arg)
42+
end
1643
end

0 commit comments

Comments
 (0)