diff --git a/README.md b/README.md index c18a525d..6a31251e 100644 --- a/README.md +++ b/README.md @@ -496,6 +496,7 @@ issue](https://github.com/brigade/overcommit/issues/238) for more details. * [ExecutePermissions](lib/overcommit/hook/pre_commit/execute_permissions.rb) * [Fasterer](lib/overcommit/hook/pre_commit/fasterer.rb) * [FixMe](lib/overcommit/hook/pre_commit/fix_me.rb) +* [Flay](lib/overcommit/hook/pre_commit/flay.rb) * [Foodcritic](lib/overcommit/hook/pre_commit/foodcritic.rb) * [ForbiddenBranches](lib/overcommit/hook/pre_commit/forbidden_branches.rb) * [GoLint](lib/overcommit/hook/pre_commit/go_lint.rb) diff --git a/config/default.yml b/config/default.yml index 34cb6cd6..6ba21f37 100644 --- a/config/default.yml +++ b/config/default.yml @@ -283,6 +283,16 @@ PreCommit: flags: ['-IEHnw'] keywords: ['BROKEN', 'BUG', 'ERROR', 'FIXME', 'HACK', 'NOTE', 'OPTIMIZE', 'REVIEW', 'TODO', 'WTF', 'XXX'] + Flay: + enabled: false + description: 'Analyze ruby code for structural similarities with Flay' + required_executable: 'flay' + install_command: 'gem install flay' + mass_threshold: 16 + fuzzy: 1 + liberal: false + include: '**/*.rb' + Foodcritic: enabled: false description: 'Analyze with Foodcritic' diff --git a/lib/overcommit/hook/pre_commit/flay.rb b/lib/overcommit/hook/pre_commit/flay.rb new file mode 100644 index 00000000..c30ee76b --- /dev/null +++ b/lib/overcommit/hook/pre_commit/flay.rb @@ -0,0 +1,36 @@ +module Overcommit::Hook::PreCommit + # Runs `flay` against any modified files. + # + # @see https://github.com/seattlerb/flay + class Flay < Base + # Flay prints two kinds of messages: + # + # 1) IDENTICAL code found in :defn (mass*2 = MASS) + # file_path_1.rb:LINE_1 + # file_path_2.rb:LINE_2 + # + # 2) Similar code found in :defn (mass = MASS) + # file_path_1.rb:LINE_1 + # file_path_2.rb:LINE_2 + # + + def run + command = ['flay', '--mass', @config['mass_threshold'].to_s, '--fuzzy', @config['fuzzy'].to_s] + # Use a more liberal detection method + command += ['--liberal'] if @config['liberal'] + messages = [] + # Run the command for each file + applicable_files.each do |file| + result = execute(command, args: [file]) + results = result.stdout.split("\n\n") + results.shift + unless results.empty? + error_message = results.join("\n").gsub(/^\d+\)\s*/, '') + message = Overcommit::Hook::Message.new(:error, nil, nil, error_message) + messages << message + end + end + messages + end + end +end diff --git a/spec/overcommit/hook/pre_commit/flay_spec.rb b/spec/overcommit/hook/pre_commit/flay_spec.rb new file mode 100644 index 00000000..e2b032da --- /dev/null +++ b/spec/overcommit/hook/pre_commit/flay_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe Overcommit::Hook::PreCommit::Flay do + let(:config) { Overcommit::ConfigurationLoader.default_configuration } + let(:context) { double('context') } + let(:applicable_files) { %w[file1.rb] } + subject { described_class.new(config, context) } + + before do + subject.stub(:applicable_files).and_return(applicable_files) + end + + around do |example| + repo do + example.run + end + end + + before do + command = %w[flay --mass 16 --fuzzy 1] + subject.stub(:execute).with(command, args: applicable_files).and_return(result) + end + + context 'flay discovered two issues' do + let(:result) do + double( + success?: false, + stdout: <<-MSG +Total score (lower is better) = 268 + +1) IDENTICAL code found in :defn (mass*2 = 148) + app/whatever11.rb:105 + app/whatever12.rb:76 + +2) Similar code found in :defn (mass = 120) + app/whatever21.rb:105 + app/whatever22.rb:76 + +MSG + ) + end + + it { should fail_hook } + end + + context 'flay discovered no issues' do + let(:result) do + double( + success?: false, + stdout: <<-MSG +Total score (lower is better) = 0 +MSG + ) + end + + it { should pass } + end +end