Skip to content

Commit f6ecd43

Browse files
diegojromerolopezsds
authored andcommitted
[FEATURE] Pre-commit hook: check your yard documentation coverage (sds#584)
* [FEATURE] Pre-commit hook: check your yard documentation coverage * Fixes for rubocop Fix some issues detected by Rubocop: CheckYardCoverage hook: run was too big and lines too long * Fix for rubocop Removing empty, following Rubocop orders * [FIX] Fix command The earlier command createded documentation files in ./doc. The current command does not. * [FIX] Bad stats extraction
1 parent f06fc8a commit f6ecd43

File tree

4 files changed

+197
-0
lines changed

4 files changed

+197
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ issue](https://github.com/brigade/overcommit/issues/238) for more details.
488488
* [BundleOutdated](lib/overcommit/hook/pre_commit/bundle_outdated.rb)
489489
* [`*`CaseConflicts](lib/overcommit/hook/pre_commit/case_conflicts.rb)
490490
* [ChamberSecurity](lib/overcommit/hook/pre_commit/chamber_security.rb)
491+
* [CheckYardCoverage](lib/overcommit/hook/pre_commit/check_yard_coverage.rb)
491492
* [CoffeeLint](lib/overcommit/hook/pre_commit/coffee_lint.rb)
492493
* [Credo](lib/overcommit/hook/pre_commit/credo.rb)
493494
* [CssLint](lib/overcommit/hook/pre_commit/css_lint.rb)

config/default.yml

+11
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,17 @@ PreCommit:
222222
install_command: 'gem install chamber'
223223
include: *chamber_settings_files
224224

225+
CheckYardCoverage:
226+
enabled: false
227+
description: 'Checking for yard coverage'
228+
command: ['yard', 'stats', '--list-undoc', '--compact']
229+
flags: ['--private', '--protected']
230+
required_executable: 'yard'
231+
install_command: 'gem install yard'
232+
min_coverage_percentage: 100
233+
include:
234+
- '/**/*.rb'
235+
225236
CoffeeLint:
226237
enabled: false
227238
description: 'Analyze with coffeelint'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
2+
module Overcommit::Hook::PreCommit
3+
# Class to check yard documentation coverage.
4+
#
5+
# Use option "min_coverage_percentage" in your CheckYardCoverage configuration
6+
# to set your desired documentation coverage percentage.
7+
#
8+
class CheckYardCoverage < Base
9+
def run
10+
# Run a no-stats yard command to get the coverage
11+
args = flags + applicable_files
12+
result = execute(command, args: args)
13+
14+
warnings_and_stats_text, undocumented_objects_text =
15+
result.stdout.split('Undocumented Objects:')
16+
17+
warnings_and_stats = warnings_and_stats_text.strip.split("\n")
18+
19+
# Stats are the last 7 lines before the undocumented objects
20+
stats = warnings_and_stats.slice(-7, 7)
21+
22+
# If no stats present (shouldn't happen), warn the user and end
23+
if stats.class != Array || stats.length != 7
24+
return [:warn, 'Impossible to read the yard stats. Please, check your yard installation.']
25+
end
26+
27+
# Check the yard coverage
28+
yard_coverage = check_yard_coverage(stats)
29+
if yard_coverage == :warn
30+
return [
31+
:warn,
32+
'Impossible to read yard doc coverage. Please, check your yard installation.'
33+
]
34+
end
35+
return :pass if yard_coverage == :pass
36+
37+
error_messages(yard_coverage, undocumented_objects_text)
38+
end
39+
40+
private
41+
42+
# Check the yard coverage
43+
#
44+
# Return a :pass if the coverage is enough, :warn if it couldn't be read,
45+
# otherwise, it has been read successfully.
46+
#
47+
def check_yard_coverage(stat_lines)
48+
if config['min_coverage_percentage']
49+
match = stat_lines.last.match(/^\s*([\d.]+)%\s+documented\s*$/)
50+
unless match
51+
return :warn
52+
end
53+
54+
yard_coverage = match.captures[0].to_f
55+
if yard_coverage >= config['min_coverage_percentage'].to_f
56+
return :pass
57+
end
58+
59+
yard_coverage
60+
end
61+
end
62+
63+
# Create the error messages
64+
def error_messages(yard_coverage, error_text)
65+
first_message = "You have a #{yard_coverage}% yard documentation coverage. "\
66+
"#{config['min_coverage_percentage']}% is the minimum required."
67+
68+
# Add the undocumented objects text as error messages
69+
messages = [Overcommit::Hook::Message.new(:error, nil, nil, first_message)]
70+
71+
errors = error_text.strip.split("\n")
72+
errors.each do |undocumented_object|
73+
undocumented_object_message, file_info = undocumented_object.split(/:?\s+/)
74+
file_info_match = file_info.match(/^\(([^:]+):(\d+)\)/)
75+
76+
# In case any compacted error does not follow the format, ignore it
77+
if file_info_match
78+
file = file_info_match.captures[0]
79+
line = file_info_match.captures[1]
80+
messages << Overcommit::Hook::Message.new(
81+
:error, file, line, "#{file}:#{line}: #{undocumented_object_message}"
82+
)
83+
end
84+
end
85+
messages
86+
end
87+
end
88+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
require 'spec_helper'
2+
3+
describe Overcommit::Hook::PreCommit::CheckYardCoverage do
4+
let(:config) { Overcommit::ConfigurationLoader.default_configuration }
5+
let(:context) { double('context') }
6+
subject { described_class.new(config, context) }
7+
8+
before do
9+
subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb])
10+
end
11+
12+
context 'when yard exits successfully' do
13+
before do
14+
result = double('result')
15+
result.stub(:stdout).and_return(
16+
<<-HEREDOC
17+
Files: 72
18+
Modules: 12 ( 0 undocumented)
19+
Classes: 63 ( 0 undocumented)
20+
Constants: 91 ( 0 undocumented)
21+
Attributes: 11 ( 0 undocumented)
22+
Methods: 264 ( 0 undocumented)
23+
100.0% documented
24+
HEREDOC
25+
)
26+
subject.stub(:execute).and_return(result)
27+
end
28+
29+
it { should pass }
30+
end
31+
32+
context 'when somehow yard exits a non-stats output' do
33+
before do
34+
result = double('result')
35+
result.stub(:stdout).and_return(
36+
<<-HEREDOC
37+
WHATEVER OUTPUT THAT IS NOT YARD STATS ONE
38+
HEREDOC
39+
)
40+
subject.stub(:execute).and_return(result)
41+
end
42+
43+
it { should warn }
44+
end
45+
46+
context 'when somehow yard coverage is not a valid value' do
47+
before do
48+
result = double('result')
49+
result.stub(:stdout).and_return(
50+
<<-HEREDOC
51+
Files: 72
52+
Modules: 12 ( 0 undocumented)
53+
Classes: 63 ( 0 undocumented)
54+
Constants: 91 ( 0 undocumented)
55+
Attributes: 11 ( 0 undocumented)
56+
Methods: 264 ( 0 undocumented)
57+
AAAAAA documented
58+
HEREDOC
59+
)
60+
subject.stub(:execute).and_return(result)
61+
end
62+
63+
it { should warn }
64+
end
65+
66+
context 'when yard exits unsucessfully' do
67+
let(:result) { double('result') }
68+
69+
before do
70+
result.stub(:success?).and_return(false)
71+
subject.stub(:execute).and_return(result)
72+
end
73+
74+
context 'and it reports an error' do
75+
before do
76+
result.stub(:stdout).and_return(
77+
<<-HEREDOC
78+
Files: 72
79+
Modules: 12 ( 3 undocumented)
80+
Classes: 63 ( 15 undocumented)
81+
Constants: 91 ( 79 undocumented)
82+
Attributes: 11 ( 0 undocumented)
83+
Methods: 264 ( 55 undocumented)
84+
65.53% documented
85+
86+
Undocumented Objects:
87+
ApplicationCable (app/channels/application_cable/channel.rb:1)
88+
ApplicationCable::Channel (app/channels/application_cable/channel.rb:2)
89+
ApplicationCable::Connection (app/channels/application_cable/connection.rb:2)
90+
HEREDOC
91+
)
92+
end
93+
94+
it { should fail_hook }
95+
end
96+
end
97+
end

0 commit comments

Comments
 (0)