-
-
Save MaxLap/ea4b6d1df81de3024562798b5501b9c8 to your computer and use it in GitHub Desktop.
A Rubocop wrapper that checks only added/modified code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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. | |
# | |
# --courage Without this option, only the modified lines | |
# are inspected. When supplied, it will check | |
# the full contents of the file. You should have | |
# the courage to fix your style violations as | |
# you see them, you know. | |
# | |
# Caveat emptor: | |
# | |
# * Monkey patching ahead. This script relies on Rubocop internals and | |
# has been tested against 1.62.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. | |
# Latest source: https://gist.github.com/MaxLap/ea4b6d1df81de3024562798b5501b9c8 | |
require 'rubocop' | |
require 'pathname' | |
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, only_changed_lines = true) | |
@files = files_modified_since(ref) | |
@line_filter = build_line_filter(@files, ref) if only_changed_lines | |
end | |
def process_bribe | |
eat_a_donut if ARGV.empty? | |
ref = nil | |
only_changed_lines = true | |
loop do | |
arg = ARGV.shift | |
case arg | |
when '--local' | |
ref = `git rev-parse --abbrev-ref --symbolic-full-name @{u}`.chomp | |
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 master`.chomp | |
when '--courage' | |
only_changed_lines = false | |
else | |
ARGV.unshift arg | |
break | |
end | |
end | |
return unless ref | |
cover_up_unmodified ref, only_changed_lines | |
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(files, ref) | |
result = {} | |
suspects = files_modified_since(ref) | |
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_method :find_unpatched, :find | |
def find(args, mode) | |
every_files = find_unpatched(args, mode) | |
modified_files = DirtyCop.uncovered_targets | |
every_files & modified_files if modified_files | |
end | |
end | |
class Runner | |
alias_method :inspect_file_unpatched, :inspect_file | |
def inspect_file(processed_source, *args) | |
offenses, updated = inspect_file_unpatched(processed_source, *args) | |
offenses = DirtyCop.bury_evidences(processed_source.path, offenses) | |
[offenses, updated] | |
end | |
alias_method :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_method :from_file_unpatched, :from_file | |
end | |
def self.from_file(path, ruby_version, **kwargs) | |
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, **kwargs) | |
else | |
from_file_unpatched(path, ruby_version, **kwargs) | |
end | |
end | |
end | |
end | |
DirtyCop.process_bribe | |
exit RuboCop::CLI.new.run | |
Updated script for Rubocop 1.62.1
What @juanfer0002 mentions was caused by rubocop/rubocop@e71d77f, so just removing that code means that redundant disable might be wrong.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
At some point this will stop working, so you probably want to remove the alias in line :177 and the method in line :178. These:
Note: This happened in my localhost, and it just stopped workign out of nowhere.