diff --git a/README.md b/README.md index 6c5a4c9..14f63f0 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Or install it yourself as: Usage: rubocop-git [options] [[commit] commit] -c, --config FILE Specify configuration file -r, --require FILE Require Ruby file + -a, --auto-correct Auto-correct offenses -d, --debug Display debug info -D, --display-cop-names Display cop names in offense messages --cached git diff --cached diff --git a/lib/rubocop/git.rb b/lib/rubocop/git.rb index 86034e7..bf95227 100644 --- a/lib/rubocop/git.rb +++ b/lib/rubocop/git.rb @@ -1,5 +1,7 @@ require 'rubocop/git/version' require 'rubocop' +require 'tempfile' +require 'fileutils' module RuboCop module Git @@ -15,5 +17,6 @@ module Git autoload :Runner, 'rubocop/git/runner' autoload :StyleChecker, 'rubocop/git/style_checker' autoload :StyleGuide, 'rubocop/git/style_guide' + autoload :RuboComment, 'rubocop/git/rubo_comment' end end diff --git a/lib/rubocop/git/cli.rb b/lib/rubocop/git/cli.rb index b302510..ed53bf3 100644 --- a/lib/rubocop/git/cli.rb +++ b/lib/rubocop/git/cli.rb @@ -35,6 +35,10 @@ def option_parser require file end + opt.on('-a', '--auto-correct', 'Auto-correct offenses.') do + @options.rubocop[:auto_correct] = true + end + opt.on('-d', '--debug', 'Display debug info') do @options.rubocop[:debug] = true end diff --git a/lib/rubocop/git/patch.rb b/lib/rubocop/git/patch.rb index 02da7f5..bab4081 100644 --- a/lib/rubocop/git/patch.rb +++ b/lib/rubocop/git/patch.rb @@ -4,15 +4,17 @@ class Patch RANGE_INFORMATION_LINE = /^@@ .+\+(?\d+),/ MODIFIED_LINE = /^\+(?!\+|\+)/ NOT_REMOVED_LINE = /^[^-]/ + PATCH_INFO_LINE = /\+([0-9,]+)/ def initialize(body) @body = body || '' + @changes = [] end def additions line_number = 0 - lines.each_with_index.inject([]) do |additions, (content, patch_position)| + lines.each_with_index.inject(@changes) do |additions, (content, patch_position)| case content when RANGE_INFORMATION_LINE line_number = Regexp.last_match[:line_number].to_i @@ -27,6 +29,31 @@ def additions end end + # maps out additions line numbers to indicate start and end of code changes + # [[5,7], [11,11]] indicates changes from line 5, 6, 7 and then + # another one at 11 + def additions_map + if @changes.empty? + self.additions + end + + map = [] + starting_line = ending_line = 0 + + @changes.each do |addition| + if starting_line == 0 + starting_line = ending_line = addition.line_number + elsif addition.line_number == ( ending_line + 1 ) + ending_line = addition.line_number + else # this row is not part of the last rows "group" + map.push([starting_line, ending_line]) + starting_line = ending_line = addition.line_number + end + end + map.push([starting_line, ending_line]) + map + end + private def lines diff --git a/lib/rubocop/git/rubo_comment.rb b/lib/rubocop/git/rubo_comment.rb new file mode 100644 index 0000000..d2c53bc --- /dev/null +++ b/lib/rubocop/git/rubo_comment.rb @@ -0,0 +1,92 @@ +module RuboCop::Git +class RuboComment + RUBOCOP_DISABLE = "# rubocop:disable all\n" + RUBOCOP_ENABLE = "# rubocop:enable all\n" + WHITE_SPACE = /^\s*/ + + def initialize(files) + @edit_map = {} + @files = files + end + + # adds rubocop enable and disabled comments to files + # making sure only edited lines are processed by rubocop + def add_comments + @files.each do |file| + patch_info = Patch.new(file.patch).additions_map + temp_file = Tempfile.new('temp') + + line_count = edited_line_count = current_patch = 0 + in_patch = false + edit_locations = [] + + begin + File.open(file.filename, "r").each_line do |line| + line_count += 1 + edited_line_count += 1 + + if line_count == patch_info[current_patch].first + temp_file.puts generate_spaces(line) + RUBOCOP_ENABLE + in_patch = true + edit_locations.push edited_line_count + edited_line_count += 1 + + elsif in_patch && patch_info[current_patch].last + 1 == line_count + temp_file.puts generate_spaces(line) + RUBOCOP_DISABLE + in_patch = false + edit_locations.push edited_line_count + edited_line_count += 1 + current_patch += 1 unless (current_patch + 1) >= patch_info.size + + elsif line_count == 1 #adds disable at top of file + temp_file.puts generate_spaces(line) + RUBOCOP_DISABLE + edit_locations.push edited_line_count + edited_line_count += 1 + + end + temp_file.puts line + end + + temp_file.close + FileUtils.mv(temp_file.path, file.filename) + @edit_map[file.filename] = edit_locations + ensure + temp_file.close + temp_file.unlink + end + end + end + + # removes all added comments that where added from add_comments + def remove_comments + @files.each do |file| + temp_file = Tempfile.new('temp') + line_count = 0 + + begin + File.open(file.filename, "r").each_line do |line| + line_count += 1 + temp_file.puts line unless @edit_map[file.filename].find_index line_count + end + + temp_file.close + FileUtils.mv(temp_file.path, file.filename) + ensure + temp_file.close + temp_file.unlink + end + end + end + + private + + # generates whitespaces to make en/disable comments match line indent + # preventing rubocop errors + def generate_spaces(line) + whitespaces = "" + WHITE_SPACE.match(line).to_s.split('').size.times { whitespaces << " " } + whitespaces + end + +end +end diff --git a/lib/rubocop/git/runner.rb b/lib/rubocop/git/runner.rb index 1264ab3..4945e2a 100644 --- a/lib/rubocop/git/runner.rb +++ b/lib/rubocop/git/runner.rb @@ -9,8 +9,15 @@ def run(options) @options = options @files = DiffParser.parse(git_diff(options)) + rubo_comment = RuboComment.new(@files) + + #adds comments to files and reparses diff after changes are made + rubo_comment.add_comments + @files = DiffParser.parse(git_diff(options)) display_violations($stdout) + #removes comments after rubocop processing + rubo_comment.remove_comments exit(1) if violations.any? end @@ -45,7 +52,7 @@ def git_diff(options) def display_violations(io) formatter = RuboCop::Formatter::ClangStyleFormatter.new(io) formatter.started(nil) - + violations.map do |violation| formatter.file_finished( violation.filename,