Inspired and adapted from https://gist.github.com/skanev/9d4bec97d5a6825eaaf6 https://gist.github.com/MaxLap/ea4b6d1df81de3024562798b5501b9c8pull/10894/head
parent
e25d2f14ee
commit
d1ce52f9f0
@ -0,0 +1,202 @@ |
||||
#!/usr/bin/env ruby |
||||
|
||||
# A sneaky wrapper around Rubocop that allows you to run it only against |
||||
# the recent changes, as opposed to the whole project. It lets you |
||||
# enforce the style guide for new/modified code only, as opposed to |
||||
# having to restyle everything or adding cops incrementally. It relies |
||||
# on git to figure out which files to check. |
||||
# |
||||
# Here are some options you can pass in addition to the ones in rubocop: |
||||
# |
||||
# --local Check only the changes you are about to push |
||||
# to the remote repository. |
||||
# |
||||
# --staged Check only changes that are currently staged. |
||||
# |
||||
# --uncommitted Check only changes in files that have not been |
||||
# --index committed (i.e. either in working directory or |
||||
# staged). |
||||
# |
||||
# --against REFSPEC Check changes since REFSPEC. This can be |
||||
# anything that git will recognize. |
||||
# |
||||
# --branch Check only changes in the current branch. |
||||
# |
||||
# Caveat emptor: |
||||
# |
||||
# * Monkey patching ahead. This script relies on Rubocop internals and |
||||
# has been tested against 1.30.1. Newer (or older) versions might |
||||
# break it. |
||||
# |
||||
# * While it does try to check modified lines only, there might be some |
||||
# quirks. It might not show offenses in modified code if they are |
||||
# reported at unmodified lines. It might also show offenses in |
||||
# unmodified code if they are reported in modified lines. |
||||
|
||||
# Inspired and adapted from |
||||
# https://gist.github.com/skanev/9d4bec97d5a6825eaaf6 |
||||
# https://gist.github.com/MaxLap/ea4b6d1df81de3024562798b5501b9c8 |
||||
|
||||
require 'rubocop' |
||||
|
||||
module DirtyCop |
||||
extend self # In your face, style guide! |
||||
|
||||
def bury_evidence?(file, line) |
||||
!report_offense_at?(file, line) |
||||
end |
||||
|
||||
def bury_evidences(file, offenses) |
||||
offenses.reject { |o| bury_evidence?(file, o.line) } |
||||
end |
||||
|
||||
def staged_changes_only? |
||||
!!@staged_changes_only |
||||
end |
||||
|
||||
def uncovered_targets |
||||
@files |
||||
end |
||||
|
||||
def cover_up_unmodified(ref) |
||||
@files = files_modified_since(ref) |
||||
@line_filter = build_line_filter(@files, ref) |
||||
end |
||||
|
||||
def process_bribe |
||||
eat_a_donut if ARGV.empty? |
||||
|
||||
ref = nil |
||||
|
||||
loop do |
||||
arg = ARGV.shift |
||||
case arg |
||||
when '--local' |
||||
ref = `git rev-parse --abbrev-ref --symbolic-full-name @{u}` |
||||
exit 1 unless $?.success? |
||||
when '--staged' |
||||
ref = '--cached' |
||||
@staged_changes_only = true |
||||
ARGV << "--cache=false" |
||||
when '--against' |
||||
ref = ARGV.shift |
||||
when '--uncommitted', '--index' |
||||
ref = 'HEAD' |
||||
when '--branch' |
||||
ref = `git merge-base HEAD dev` |
||||
else |
||||
ARGV.unshift arg |
||||
break |
||||
end |
||||
end |
||||
|
||||
return unless ref |
||||
|
||||
cover_up_unmodified ref.chomp |
||||
end |
||||
|
||||
private |
||||
|
||||
def report_offense_at?(file, line) |
||||
@line_filter && @line_filter.fetch(file)[line] |
||||
end |
||||
|
||||
def files_modified_since(ref) |
||||
`git diff --diff-filter=AM --name-only #{ref}` |
||||
.lines |
||||
.map(&:chomp) |
||||
.map { |file| File.absolute_path(file) } |
||||
end |
||||
|
||||
def build_line_filter(suspects, ref) |
||||
result = {} |
||||
|
||||
suspects.each do |file| |
||||
result[file] = lines_modified_since(file, ref) |
||||
end |
||||
|
||||
result |
||||
end |
||||
|
||||
def lines_modified_since(file, ref) |
||||
ranges = |
||||
`git diff -p -U0 #{ref} #{file}` |
||||
.lines |
||||
.grep(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/) { $1.to_i...($1.to_i + ($2 || 1).to_i) } |
||||
.reverse |
||||
|
||||
return [] if ranges.empty? |
||||
|
||||
mask = Array.new(ranges.first.end) |
||||
|
||||
ranges.each do |range| |
||||
range.each do |line| |
||||
mask[line] = true |
||||
end |
||||
end |
||||
|
||||
mask |
||||
end |
||||
|
||||
def eat_a_donut |
||||
puts "#$PROGRAM_NAME: The dirty cop Alex Murphy could have been" |
||||
puts |
||||
puts File.read(__FILE__)[/(?:^#(?:[^!].*)?\n)+/s].gsub(/^#/, ' ') |
||||
exit |
||||
end |
||||
end |
||||
|
||||
module RuboCop |
||||
class TargetFinder |
||||
alias find_unpatched find |
||||
|
||||
def find(...) |
||||
every_files = find_unpatched(...) |
||||
modified_files = DirtyCop.uncovered_targets |
||||
|
||||
if modified_files |
||||
every_files & modified_files |
||||
else |
||||
every_files |
||||
end |
||||
end |
||||
end |
||||
|
||||
class Runner |
||||
alias inspect_file_unpatched inspect_file |
||||
|
||||
def inspect_file(file, *args) |
||||
offenses, updated = inspect_file_unpatched(file, *args) |
||||
offenses = offenses.reject { |o| DirtyCop.bury_evidence?(file.path, o.line) } |
||||
[offenses, updated] |
||||
end |
||||
|
||||
alias add_redundant_disables_unpatched add_redundant_disables |
||||
def add_redundant_disables(file, offenses, source) |
||||
offenses = add_redundant_disables_unpatched(file, offenses, source) |
||||
DirtyCop.bury_evidences(file, offenses) |
||||
end |
||||
end |
||||
|
||||
class ProcessedSource |
||||
class << self |
||||
alias from_file_unpatched from_file |
||||
end |
||||
|
||||
def self.from_file(path, ruby_version) |
||||
if DirtyCop.staged_changes_only? |
||||
pathname = Pathname.new(path) |
||||
git_root = Pathname.new(`git rev-parse --show-toplevel`.strip) |
||||
git_relative_path = pathname.relative_path_from(git_root).to_s |
||||
source = `git show :#{git_relative_path}` |
||||
new(source, ruby_version, path) |
||||
else |
||||
from_file_unpatched(path, ruby_version) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
DirtyCop.process_bribe |
||||
|
||||
exit RuboCop::CLI.new.run |
Loading…
Reference in new issue