From 9f499d6d0e7f30f869f6680bfa81c89fdc4c3e60 Mon Sep 17 00:00:00 2001 From: Diego J Date: Fri, 6 Jul 2018 17:55:52 +0200 Subject: [PATCH 1/5] [FEATURE] Pre-commit hook: check your yard documentation coverage --- README.md | 1 + config/default.yml | 11 +++ .../hook/pre_commit/check_yard_coverage.rb | 59 +++++++++++ .../pre_commit/check_yard_coverage_spec.rb | 97 +++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 lib/overcommit/hook/pre_commit/check_yard_coverage.rb create mode 100644 spec/overcommit/hook/pre_commit/check_yard_coverage_spec.rb diff --git a/README.md b/README.md index c18a525d..c47cfd37 100644 --- a/README.md +++ b/README.md @@ -488,6 +488,7 @@ issue](https://github.com/brigade/overcommit/issues/238) for more details. * [BundleOutdated](lib/overcommit/hook/pre_commit/bundle_outdated.rb) * [`*`CaseConflicts](lib/overcommit/hook/pre_commit/case_conflicts.rb) * [ChamberSecurity](lib/overcommit/hook/pre_commit/chamber_security.rb) +* [CheckYardCoverage](lib/overcommit/hook/pre_commit/check_yard_coverage.rb) * [CoffeeLint](lib/overcommit/hook/pre_commit/coffee_lint.rb) * [Credo](lib/overcommit/hook/pre_commit/credo.rb) * [CssLint](lib/overcommit/hook/pre_commit/css_lint.rb) diff --git a/config/default.yml b/config/default.yml index 34cb6cd6..e8bd1703 100644 --- a/config/default.yml +++ b/config/default.yml @@ -222,6 +222,17 @@ PreCommit: install_command: 'gem install chamber' include: *chamber_settings_files + CheckYardCoverage: + enabled: false + description: 'Checking for yard coverage' + command: 'yard' + flags: ['--private', 'protected'] + required_executable: 'yard' + install_command: 'gem install yard' + min_coverage_percentage: 100 + include: + - '/**/*.rb' + CoffeeLint: enabled: false description: 'Analyze with coffeelint' diff --git a/lib/overcommit/hook/pre_commit/check_yard_coverage.rb b/lib/overcommit/hook/pre_commit/check_yard_coverage.rb new file mode 100644 index 00000000..8bf26936 --- /dev/null +++ b/lib/overcommit/hook/pre_commit/check_yard_coverage.rb @@ -0,0 +1,59 @@ + +module Overcommit::Hook::PreCommit + # Class to check yard documentation coverage. + # + # Use option "min_coverage_percentage" in your CheckYardCoverage configuration + # to set your desired documentation coverage percentage. + # + class CheckYardCoverage < Base + def run + # Run a no-stats yard command to get the coverage + args = %w[-n --no-save --list-undoc --compact] + flags + applicable_files + result = execute(command, args: args) + + warnings_and_stats_text, undocumented_objects_text = result.stdout.split('Undocumented Objects:') + + warnings_and_stats = warnings_and_stats_text.strip.split("\n") + + # Stats are the last 7 lines before the undocumented objects + stats = warnings_and_stats[-7..100] + + # If no stats present (shouldn't happen), warn the user and end + unless stats + return [:warn, 'Impossible to read the yard stats. Please, check your yard installation.'] + end + + # Check the yard doc coverage percentage + yard_coverage = nil + if config['min_coverage_percentage'] + match = stats.last.match(/^\s*([\d.]+)%\s+documented\s*$/) + if match + yard_coverage = match.captures[0].to_f + if yard_coverage >= config['min_coverage_percentage'].to_f + return :pass + end + else + return [:warn, 'Impossible to read the yard documentation coverage. Please, check your yard installation.'] + end + end + + first_message = "You have a #{yard_coverage}% yard documentation coverage. "\ + "#{config['min_coverage_percentage']}% is the minimum required." + + # Add the undocumented objects text as error messages + messages = [Overcommit::Hook::Message.new(:error, nil, nil, first_message)] + undocumented_objects = undocumented_objects_text.strip.split("\n") + undocumented_objects.each do |undocumented_object| + undocumented_object_message, file_info = undocumented_object.split(/:?\s+/) + file_info_match = file_info.match(/^\(([^:]+):(\d+)\)/) + # In case any compacted error does not follow the format, ignore it + if file_info_match + file = file_info_match.captures[0] + line = file_info_match.captures[1] + messages << Overcommit::Hook::Message.new(:error, file, line, "#{file}:#{line}: #{undocumented_object_message}") + end + end + messages + end + end +end diff --git a/spec/overcommit/hook/pre_commit/check_yard_coverage_spec.rb b/spec/overcommit/hook/pre_commit/check_yard_coverage_spec.rb new file mode 100644 index 00000000..cdf7627d --- /dev/null +++ b/spec/overcommit/hook/pre_commit/check_yard_coverage_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper' + +describe Overcommit::Hook::PreCommit::CheckYardCoverage do + let(:config) { Overcommit::ConfigurationLoader.default_configuration } + let(:context) { double('context') } + subject { described_class.new(config, context) } + + before do + subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb]) + end + + context 'when yard exits successfully' do + before do + result = double('result') + result.stub(:stdout).and_return( + <<-HEREDOC + Files: 72 + Modules: 12 ( 0 undocumented) + Classes: 63 ( 0 undocumented) + Constants: 91 ( 0 undocumented) + Attributes: 11 ( 0 undocumented) + Methods: 264 ( 0 undocumented) + 100.0% documented + HEREDOC + ) + subject.stub(:execute).and_return(result) + end + + it { should pass } + end + + context 'when somehow yard exits a non-stats output' do + before do + result = double('result') + result.stub(:stdout).and_return( + <<-HEREDOC + WHATEVER OUTPUT THAT IS NOT YARD STATS ONE + HEREDOC + ) + subject.stub(:execute).and_return(result) + end + + it { should warn } + end + + context 'when somehow yard coverage is not a valid value' do + before do + result = double('result') + result.stub(:stdout).and_return( + <<-HEREDOC + Files: 72 + Modules: 12 ( 0 undocumented) + Classes: 63 ( 0 undocumented) + Constants: 91 ( 0 undocumented) + Attributes: 11 ( 0 undocumented) + Methods: 264 ( 0 undocumented) + AAAAAA documented + HEREDOC + ) + subject.stub(:execute).and_return(result) + end + + it { should warn } + end + + context 'when yard exits unsucessfully' do + let(:result) { double('result') } + + before do + result.stub(:success?).and_return(false) + subject.stub(:execute).and_return(result) + end + + context 'and it reports an error' do + before do + result.stub(:stdout).and_return( + <<-HEREDOC + Files: 72 + Modules: 12 ( 3 undocumented) + Classes: 63 ( 15 undocumented) + Constants: 91 ( 79 undocumented) + Attributes: 11 ( 0 undocumented) + Methods: 264 ( 55 undocumented) + 65.53% documented + + Undocumented Objects: + ApplicationCable (app/channels/application_cable/channel.rb:1) + ApplicationCable::Channel (app/channels/application_cable/channel.rb:2) + ApplicationCable::Connection (app/channels/application_cable/connection.rb:2) + HEREDOC + ) + end + + it { should fail_hook } + end + end +end From dcb2c8560e9d3c2f18d050059709b100c4e5e852 Mon Sep 17 00:00:00 2001 From: Diego J Date: Fri, 6 Jul 2018 18:32:30 +0200 Subject: [PATCH 2/5] Fixes for rubocop Fix some issues detected by Rubocop: CheckYardCoverage hook: run was too big and lines too long --- .../hook/pre_commit/check_yard_coverage.rb | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/lib/overcommit/hook/pre_commit/check_yard_coverage.rb b/lib/overcommit/hook/pre_commit/check_yard_coverage.rb index 8bf26936..84227dd8 100644 --- a/lib/overcommit/hook/pre_commit/check_yard_coverage.rb +++ b/lib/overcommit/hook/pre_commit/check_yard_coverage.rb @@ -11,7 +11,8 @@ def run args = %w[-n --no-save --list-undoc --compact] + flags + applicable_files result = execute(command, args: args) - warnings_and_stats_text, undocumented_objects_text = result.stdout.split('Undocumented Objects:') + warnings_and_stats_text, undocumented_objects_text = + result.stdout.split('Undocumented Objects:') warnings_and_stats = warnings_and_stats_text.strip.split("\n") @@ -23,37 +24,65 @@ def run return [:warn, 'Impossible to read the yard stats. Please, check your yard installation.'] end - # Check the yard doc coverage percentage - yard_coverage = nil + # Check the yard coverage + yard_coverage = check_yard_coverage(stats) + if yard_coverage == :warn + return [ + :warn, + 'Impossible to read yard doc coverage. Please, check your yard installation.' + ] + end + return :pass if yard_coverage == :pass + + error_messages(yard_coverage, undocumented_objects_text) + end + + # Check the yard coverage + # + # Return a :pass if the coverage is enough, :warn if it couldn't be read, + # otherwise, it has been read successfully. + # + def check_yard_coverage(stat_lines) if config['min_coverage_percentage'] - match = stats.last.match(/^\s*([\d.]+)%\s+documented\s*$/) - if match - yard_coverage = match.captures[0].to_f - if yard_coverage >= config['min_coverage_percentage'].to_f - return :pass - end - else - return [:warn, 'Impossible to read the yard documentation coverage. Please, check your yard installation.'] + match = stat_lines.last.match(/^\s*([\d.]+)%\s+documented\s*$/) + unless match + return :warn + end + + yard_coverage = match.captures[0].to_f + if yard_coverage >= config['min_coverage_percentage'].to_f + return :pass end + + yard_coverage end + end + # Create the error messages + def error_messages(yard_coverage, error_text) first_message = "You have a #{yard_coverage}% yard documentation coverage. "\ "#{config['min_coverage_percentage']}% is the minimum required." # Add the undocumented objects text as error messages messages = [Overcommit::Hook::Message.new(:error, nil, nil, first_message)] - undocumented_objects = undocumented_objects_text.strip.split("\n") - undocumented_objects.each do |undocumented_object| + + errors = error_text.strip.split("\n") + errors.each do |undocumented_object| undocumented_object_message, file_info = undocumented_object.split(/:?\s+/) file_info_match = file_info.match(/^\(([^:]+):(\d+)\)/) + # In case any compacted error does not follow the format, ignore it if file_info_match file = file_info_match.captures[0] line = file_info_match.captures[1] - messages << Overcommit::Hook::Message.new(:error, file, line, "#{file}:#{line}: #{undocumented_object_message}") + messages << Overcommit::Hook::Message.new( + :error, file, line, "#{file}:#{line}: #{undocumented_object_message}" + ) end end + messages end + end end From d3f5089e694e9e60d91b8772b2d8a1cdd789a442 Mon Sep 17 00:00:00 2001 From: Diego J Date: Fri, 6 Jul 2018 18:39:47 +0200 Subject: [PATCH 3/5] Fix for rubocop Removing empty, following Rubocop orders --- lib/overcommit/hook/pre_commit/check_yard_coverage.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/overcommit/hook/pre_commit/check_yard_coverage.rb b/lib/overcommit/hook/pre_commit/check_yard_coverage.rb index 84227dd8..19973c54 100644 --- a/lib/overcommit/hook/pre_commit/check_yard_coverage.rb +++ b/lib/overcommit/hook/pre_commit/check_yard_coverage.rb @@ -80,9 +80,7 @@ def error_messages(yard_coverage, error_text) ) end end - messages end - end end From 5c66dfe31d2f4bc28e26223c14ad0c656bad65a5 Mon Sep 17 00:00:00 2001 From: Diego J Date: Mon, 9 Jul 2018 12:05:12 +0200 Subject: [PATCH 4/5] [FIX] Fix command The earlier command createded documentation files in ./doc. The current command does not. --- config/default.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/default.yml b/config/default.yml index e8bd1703..94cf2d6c 100644 --- a/config/default.yml +++ b/config/default.yml @@ -225,8 +225,8 @@ PreCommit: CheckYardCoverage: enabled: false description: 'Checking for yard coverage' - command: 'yard' - flags: ['--private', 'protected'] + command: ['yard', 'stats', '--list-undoc', '--compact'] + flags: ['--private', '--protected'] required_executable: 'yard' install_command: 'gem install yard' min_coverage_percentage: 100 From 15af46957f610e574a39596a7d4cc92635a74718 Mon Sep 17 00:00:00 2001 From: Diego J Date: Mon, 9 Jul 2018 12:45:47 +0200 Subject: [PATCH 5/5] [FIX] Bad stats extraction --- lib/overcommit/hook/pre_commit/check_yard_coverage.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/overcommit/hook/pre_commit/check_yard_coverage.rb b/lib/overcommit/hook/pre_commit/check_yard_coverage.rb index 19973c54..6914ec33 100644 --- a/lib/overcommit/hook/pre_commit/check_yard_coverage.rb +++ b/lib/overcommit/hook/pre_commit/check_yard_coverage.rb @@ -8,7 +8,7 @@ module Overcommit::Hook::PreCommit class CheckYardCoverage < Base def run # Run a no-stats yard command to get the coverage - args = %w[-n --no-save --list-undoc --compact] + flags + applicable_files + args = flags + applicable_files result = execute(command, args: args) warnings_and_stats_text, undocumented_objects_text = @@ -17,10 +17,10 @@ def run warnings_and_stats = warnings_and_stats_text.strip.split("\n") # Stats are the last 7 lines before the undocumented objects - stats = warnings_and_stats[-7..100] + stats = warnings_and_stats.slice(-7, 7) # If no stats present (shouldn't happen), warn the user and end - unless stats + if stats.class != Array || stats.length != 7 return [:warn, 'Impossible to read the yard stats. Please, check your yard installation.'] end @@ -37,6 +37,8 @@ def run error_messages(yard_coverage, undocumented_objects_text) end + private + # Check the yard coverage # # Return a :pass if the coverage is enough, :warn if it couldn't be read,