diff --git a/config/default.yml b/config/default.yml index 02777c7e..98f495e1 100644 --- a/config/default.yml +++ b/config/default.yml @@ -337,6 +337,15 @@ PreCommit: flags: ['-IEHnw'] keywords: ['FContext','FDescribe','FIt','FMeasure','FSpecify','FWhen'] + GolangciLint: + enabled: false + description: 'Analyze with golangci-lint' + required_executable: 'golangci-lint' + install_command: 'go get github.com/golangci/golangci-lint/cmd/golangci-lint' + flags: ['--out-format=line-number', '--print-issued-lines=false'] + command: ['golangci-lint', 'run'] + include: '**/*.go' + GoLint: enabled: false description: 'Analyze with golint' @@ -1253,6 +1262,14 @@ PrePush: required_executable: 'git-lfs' install_command: 'brew install git-lfs' + GolangciLint: + enabled: false + description: 'Analyze with golangci-lint' + required_executable: 'golangci-lint' + install_command: 'go get github.com/golangci/golangci-lint/cmd/golangci-lint' + flags: ['--out-format=line-number', '--print-issued-lines=false'] + command: ['golangci-lint', 'run'] + Minitest: enabled: false description: 'Run Minitest test suite' diff --git a/lib/overcommit/hook/pre_commit/golangci_lint.rb b/lib/overcommit/hook/pre_commit/golangci_lint.rb new file mode 100644 index 00000000..6dffe659 --- /dev/null +++ b/lib/overcommit/hook/pre_commit/golangci_lint.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Overcommit::Hook::PreCommit + # Runs `golangci-lint run` against any modified packages + # + # @see https://github.com/golangci/golangci-lint + class GolangciLint < Base + def run + packages = applicable_files.map { |f| File.dirname(f) }.uniq + result = execute(command, args: packages) + return :pass if result.success? + return [:fail, result.stderr] unless result.stderr.empty? + + extract_messages( + result.stdout.split("\n"), + /^(?(?:\w:)?[^:]+):(?\d+)/, + nil + ) + end + end +end diff --git a/lib/overcommit/hook/pre_push/golangci_lint.rb b/lib/overcommit/hook/pre_push/golangci_lint.rb new file mode 100644 index 00000000..42df41b0 --- /dev/null +++ b/lib/overcommit/hook/pre_push/golangci_lint.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Overcommit::Hook::PrePush + # Runs golangci-lint + # + # @see https://github.com/golangci/golangci-lint + class GolangciLint < Base + def run + result = execute(command) + return :pass if result.success? + + output = result.stdout + result.stderr + [:fail, output] + end + end +end diff --git a/spec/overcommit/hook/pre_commit/golangci_lint_spec.rb b/spec/overcommit/hook/pre_commit/golangci_lint_spec.rb new file mode 100644 index 00000000..45e4a7e3 --- /dev/null +++ b/spec/overcommit/hook/pre_commit/golangci_lint_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Overcommit::Hook::PreCommit::GolangciLint do + let(:config) { Overcommit::ConfigurationLoader.default_configuration } + let(:context) { double('context') } + subject { described_class.new(config, context) } + + let(:files) do + %w[ + pkg1/file1.go + pkg1/file2.go + pkg2/file1.go + file1.go + ] + end + let(:packages) { %w[pkg1 pkg2 .] } + before do + subject.stub(:applicable_files).and_return(files) + end + + context 'when golangci-lint exits successfully' do + let(:result) { double('result') } + + before do + result.stub(success?: true, stderr: '', stdout: '') + subject.stub(:execute).and_return(result) + end + + it 'passes packages to golangci-lint' do + expect(subject).to receive(:execute).with(subject.command, args: packages) + subject.run + end + + it 'passes' do + expect(subject).to pass + end + end + + context 'when golangci-lint exits unsucessfully' do + let(:result) { double('result') } + + before do + result.stub(:success?).and_return(false) + subject.stub(:execute).and_return(result) + end + + context 'when golangci-lint returns an error' do + let(:error_message) do + 'pkg1/file1.go:8:6: exported type `Test` should have comment or be unexported (golint)' + end + + before do + result.stub(:stdout).and_return(error_message) + result.stub(:stderr).and_return('') + end + + it 'passes packages to golangci-lint' do + expect(subject).to receive(:execute).with(subject.command, args: packages) + subject.run + end + + it 'fails' do + expect(subject).to fail_hook + end + + it 'returns valid message' do + message = subject.run.last + expect(message.file).to eq 'pkg1/file1.go' + expect(message.line).to eq 8 + expect(message.content).to eq error_message + end + end + + context 'when a generic error message is written to stderr' do + let(:error_message) { 'golangci-lint: command not found' } + before do + result.stub(:stdout).and_return('') + result.stub(:stderr).and_return(error_message) + end + + it 'passes packages to golangci-lint' do + expect(subject).to receive(:execute).with(subject.command, args: packages) + subject.run + end + + it 'fails' do + expect(subject).to fail_hook + end + + it 'returns valid message' do + message = subject.run.last + expect(message).to eq error_message + end + end + end +end diff --git a/spec/overcommit/hook/pre_push/golangci_lint_spec.rb b/spec/overcommit/hook/pre_push/golangci_lint_spec.rb new file mode 100644 index 00000000..bf4bed5f --- /dev/null +++ b/spec/overcommit/hook/pre_push/golangci_lint_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Overcommit::Hook::PrePush::GolangciLint do + let(:config) { Overcommit::ConfigurationLoader.default_configuration } + let(:context) { double('context') } + subject { described_class.new(config, context) } + + context 'when golangci-lint exits successfully' do + let(:result) { double('result') } + + before do + result.stub(success?: true, stderr: '', stdout: '') + subject.stub(:execute).and_return(result) + end + + it 'passes' do + expect(subject).to pass + end + end + + context 'when golangci-lint exits unsucessfully' do + let(:result) { double('result') } + + before do + result.stub(:success?).and_return(false) + subject.stub(:execute).and_return(result) + end + + context 'when golangci-lint returns an error' do + let(:error_message) do + 'pkg1/file1.go:8:6: exported type `Test` should have comment or be unexported (golint)' + end + + before do + result.stub(:stdout).and_return(error_message) + result.stub(:stderr).and_return('') + end + + it 'fails' do + expect(subject).to fail_hook + end + + it 'returns valid message' do + message = subject.run.last + expect(message).to eq error_message + end + end + + context 'when a generic error message is written to stderr' do + let(:error_message) { 'golangci-lint: command not found' } + before do + result.stub(:stdout).and_return('') + result.stub(:stderr).and_return(error_message) + end + + it 'fails' do + expect(subject).to fail_hook + end + + it 'returns valid message' do + message = subject.run.last + expect(message).to eq error_message + end + end + end +end