cs-rubocop-git 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +32 -0
  4. data/Appraisals +31 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +26 -0
  7. data/README.md +46 -0
  8. data/Rakefile +15 -0
  9. data/bin/rubocop-git +5 -0
  10. data/gemfiles/0.24.gemfile +7 -0
  11. data/gemfiles/0.25.gemfile +7 -0
  12. data/gemfiles/0.26.gemfile +7 -0
  13. data/gemfiles/0.27.gemfile +7 -0
  14. data/gemfiles/0.28.gemfile +7 -0
  15. data/gemfiles/0.29.gemfile +7 -0
  16. data/gemfiles/0.30.gemfile +7 -0
  17. data/gemfiles/0.31.gemfile +7 -0
  18. data/gemfiles/0.32.gemfile +7 -0
  19. data/gemfiles/0.33.gemfile +7 -0
  20. data/gemfiles/0.34.gemfile +7 -0
  21. data/gemfiles/0.35.gemfile +7 -0
  22. data/gemfiles/0.36.gemfile +7 -0
  23. data/gemfiles/0.37.gemfile +7 -0
  24. data/gemfiles/0.38.gemfile +7 -0
  25. data/gemfiles/0.39.gemfile +7 -0
  26. data/gemfiles/0.40.gemfile +7 -0
  27. data/gemfiles/0.41.gemfile +7 -0
  28. data/gemfiles/0.42.gemfile +7 -0
  29. data/gemfiles/0.43.gemfile +7 -0
  30. data/gemfiles/0.44.gemfile +7 -0
  31. data/gemfiles/0.45.gemfile +7 -0
  32. data/gemfiles/0.46.gemfile +7 -0
  33. data/gemfiles/0.47.gemfile +7 -0
  34. data/gemfiles/0.48.gemfile +7 -0
  35. data/hound.yml +254 -0
  36. data/lib/rubocop/git.rb +19 -0
  37. data/lib/rubocop/git/cli.rb +67 -0
  38. data/lib/rubocop/git/commit.rb +22 -0
  39. data/lib/rubocop/git/commit_file.rb +55 -0
  40. data/lib/rubocop/git/diff_parser.rb +31 -0
  41. data/lib/rubocop/git/file_violation.rb +5 -0
  42. data/lib/rubocop/git/line.rb +8 -0
  43. data/lib/rubocop/git/options.rb +84 -0
  44. data/lib/rubocop/git/patch.rb +36 -0
  45. data/lib/rubocop/git/pseudo_pull_request.rb +36 -0
  46. data/lib/rubocop/git/pseudo_resource.rb +24 -0
  47. data/lib/rubocop/git/runner.rb +70 -0
  48. data/lib/rubocop/git/style_checker.rb +43 -0
  49. data/lib/rubocop/git/style_guide.rb +98 -0
  50. data/lib/rubocop/git/version.rb +5 -0
  51. data/rubocop-git.gemspec +27 -0
  52. data/test/rubocop/git/cli_test.rb +12 -0
  53. data/test/rubocop/git/options_test.rb +18 -0
  54. data/test/rubocop/git/runner_test.rb +23 -0
  55. data/test/test_helper.rb +2 -0
  56. metadata +172 -0
@@ -0,0 +1,19 @@
1
+ require 'rubocop/git/version'
2
+ require 'rubocop'
3
+
4
+ module RuboCop
5
+ module Git
6
+ autoload :Commit, 'rubocop/git/commit'
7
+ autoload :CommitFile, 'rubocop/git/commit_file'
8
+ autoload :DiffParser, 'rubocop/git/diff_parser'
9
+ autoload :FileViolation, 'rubocop/git/file_violation'
10
+ autoload :Line, 'rubocop/git/line'
11
+ autoload :Options, 'rubocop/git/options'
12
+ autoload :Patch, 'rubocop/git/patch'
13
+ autoload :PseudoPullRequest, 'rubocop/git/pseudo_pull_request'
14
+ autoload :PseudoResource, 'rubocop/git/pseudo_resource'
15
+ autoload :Runner, 'rubocop/git/runner'
16
+ autoload :StyleChecker, 'rubocop/git/style_checker'
17
+ autoload :StyleGuide, 'rubocop/git/style_guide'
18
+ end
19
+ end
@@ -0,0 +1,67 @@
1
+ require 'rubocop/git'
2
+ require 'optparse'
3
+
4
+ module RuboCop
5
+ module Git
6
+ class CLI
7
+ def run(args = ARGV)
8
+ @options = Options.new
9
+ parse_arguments(args)
10
+ Runner.new.run(@options)
11
+ end
12
+
13
+ private
14
+
15
+ def parse_arguments(args)
16
+ @options.commits = option_parser.parse(args)
17
+ rescue OptionParser::InvalidOption, Options::Invalid => ex
18
+ warn "ERROR: #{ex.message}"
19
+ $stderr.puts
20
+ warn option_parser
21
+ exit 1
22
+ end
23
+
24
+ def option_parser
25
+ @option_parser ||= OptionParser.new do |opt|
26
+ opt.banner << ' [[commit] commit]'
27
+
28
+ opt.on('-c', '--config FILE',
29
+ 'Specify configuration file') do |config|
30
+ @options.config = config
31
+ end
32
+
33
+ opt.on('-r', '--require FILE',
34
+ 'Require Ruby file') do |file|
35
+ require file
36
+ end
37
+
38
+ opt.on('-d', '--debug', 'Display debug info') do
39
+ @options.rubocop[:debug] = true
40
+ end
41
+
42
+ opt.on('-D', '--display-cop-names',
43
+ 'Display cop names in offense messages') do
44
+ @options.rubocop[:display_cop_names] = true
45
+ end
46
+
47
+ opt.on('--cached', 'git diff --cached') do
48
+ @options.cached = true
49
+ end
50
+
51
+ opt.on('--staged', 'synonym of --cached') do
52
+ @options.cached = true
53
+ end
54
+
55
+ opt.on('--hound', 'Hound compatibility mode') do
56
+ @options.hound = true
57
+ end
58
+
59
+ opt.on('--version', 'Display version') do
60
+ puts RuboCop::Git::VERSION
61
+ exit 0
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,22 @@
1
+ require 'shellwords'
2
+
3
+ module RuboCop
4
+ module Git
5
+ # ref. https://github.com/thoughtbot/hound/blob/d2f3933/app/models/commit.rb
6
+ class Commit
7
+ def initialize(options)
8
+ @options = options
9
+ end
10
+
11
+ def file_content(filename)
12
+ if @options.cached
13
+ `git show :#{filename.shellescape}`
14
+ elsif @options.commit_last
15
+ `git show #{@options.commit_last.shellescape}:#{filename.shellescape}`
16
+ else
17
+ File.read(filename)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,55 @@
1
+ module RuboCop::Git
2
+ # copy from https://github.com/thoughtbot/hound/blob/d2f3933/app/models/commit_file.rb
3
+ class CommitFile
4
+ def initialize(file, commit)
5
+ @file = file
6
+ @commit = commit
7
+ end
8
+
9
+ def absolute_path
10
+ @file.absolute_path
11
+ end
12
+
13
+ def filename
14
+ @file.filename
15
+ end
16
+
17
+ def content
18
+ @content ||= begin
19
+ unless removed?
20
+ @commit.file_content(filename)
21
+ end
22
+ end
23
+ end
24
+
25
+ def relevant_line?(line_number)
26
+ modified_lines.detect do |modified_line|
27
+ modified_line.line_number == line_number
28
+ end
29
+ end
30
+
31
+ def removed?
32
+ @file.status == 'removed'
33
+ end
34
+
35
+ def ruby?
36
+ filename.match(/.*\.rb$/)
37
+ end
38
+
39
+ def modified_lines
40
+ @modified_lines ||= patch.additions
41
+ end
42
+
43
+ def modified_line_at(line_number)
44
+ modified_lines.detect do |modified_line|
45
+ modified_line.line_number == line_number
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def patch
52
+ Patch.new(@file.patch)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,31 @@
1
+ module RuboCop
2
+ module Git
3
+ class DiffParser
4
+ class << self
5
+ def parse(diff)
6
+ new.parse(diff)
7
+ end
8
+ end
9
+
10
+ def parse(diff)
11
+ files = []
12
+ in_patch = false
13
+
14
+ diff.each_line do |line|
15
+ case line
16
+ when /^diff --git/
17
+ in_patch = false
18
+ when %r{^\+{3} b/(?<path>[^\t\n\r]+)}
19
+ files << PseudoResource.new(Regexp.last_match[:path])
20
+ when /^@@/
21
+ in_patch = true
22
+ end
23
+
24
+ files.last.patch << line if in_patch
25
+ end
26
+
27
+ files
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ module RuboCop::Git
2
+ # ref. https://github.com/thoughtbot/hound/blob/d2f3933/app/models/file_violation.rb
3
+ class FileViolation < Struct.new(:filename, :offenses)
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ module RuboCop::Git
2
+ # copy from https://github.com/thoughtbot/hound/blob/d2f3933/app/models/line.rb
3
+ class Line < Struct.new(:content, :line_number, :patch_position)
4
+ def ==(other_line)
5
+ content == other_line.content
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,84 @@
1
+ module RuboCop
2
+ module Git
3
+ class Options
4
+ class Invalid < StandardError; end
5
+
6
+ HOUND_DEFAULT_CONFIG_FILE =
7
+ File.expand_path('../../../../hound.yml', __FILE__)
8
+
9
+ attr_accessor :config
10
+ attr_reader :cached, :hound, :rubocop
11
+
12
+ def initialize(hash_options = nil)
13
+ @config = nil
14
+ @cached = false
15
+ @hound = false
16
+ @rubocop = {}
17
+ @commits = []
18
+
19
+ from_hash(hash_options) if hash_options
20
+ end
21
+
22
+ def cached=(cached_)
23
+ if cached_ && !@commits.empty?
24
+ fail Invalid, 'cached and commit cannot be specified together'
25
+ end
26
+ @cached = !!cached_
27
+ end
28
+
29
+ def hound=(hound_)
30
+ @hound = !!hound_
31
+ end
32
+
33
+ def rubocop=(rubocop_)
34
+ unless rubocop_.is_a?(Hash)
35
+ fail Invalid, "invalid rubocop: #{rubocop_.inspect}"
36
+ end
37
+ @rubocop = rubocop_
38
+ end
39
+
40
+ def commits=(commits)
41
+ unless commits.is_a?(Array) && commits.length <= 2
42
+ fail Invalid, "invalid commits: #{commits.inspect}"
43
+ end
44
+ if !commits.empty? && cached
45
+ fail Invalid, 'cached and commit cannot be specified together'
46
+ end
47
+ @commits = commits
48
+ end
49
+
50
+ def config_file
51
+ if hound
52
+ HOUND_DEFAULT_CONFIG_FILE
53
+ elsif config
54
+ config
55
+ elsif File.exist?(RuboCop::ConfigLoader::DOTFILE)
56
+ RuboCop::ConfigLoader::DOTFILE
57
+ else
58
+ RuboCop::ConfigLoader::DEFAULT_FILE
59
+ end
60
+ end
61
+
62
+ def commit_first
63
+ @commits.first
64
+ end
65
+
66
+ def commit_last
67
+ @commits.length == 1 ? false : @commits.last
68
+ end
69
+
70
+ private
71
+
72
+ def from_hash(hash_options)
73
+ hash_options = hash_options.dup
74
+ %w(config cached hound rubocop commits).each do |key|
75
+ value = hash_options.delete(key) || hash_options.delete(key.to_sym)
76
+ public_send("#{key}=", value)
77
+ end
78
+ unless hash_options.empty?
79
+ fail Invalid, "invalid keys: #{hash_options.keys.join(' ')}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,36 @@
1
+ module RuboCop::Git
2
+ # copy from https://github.com/thoughtbot/hound/blob/d2f3933/app/models/patch.rb
3
+ class Patch
4
+ RANGE_INFORMATION_LINE = /^@@ .+\+(?<line_number>\d+),/
5
+ MODIFIED_LINE = /^\+(?!\+|\+)/
6
+ NOT_REMOVED_LINE = /^[^-]/
7
+
8
+ def initialize(body)
9
+ @body = body || ''
10
+ end
11
+
12
+ def additions
13
+ line_number = 0
14
+
15
+ lines.each_with_index.inject([]) do |additions, (content, patch_position)|
16
+ case content
17
+ when RANGE_INFORMATION_LINE
18
+ line_number = Regexp.last_match[:line_number].to_i
19
+ when MODIFIED_LINE
20
+ additions << Line.new(content, line_number, patch_position)
21
+ line_number += 1
22
+ when NOT_REMOVED_LINE
23
+ line_number += 1
24
+ end
25
+
26
+ additions
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def lines
33
+ @body.lines
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module RuboCop
2
+ module Git
3
+ # ref. https://github.com/thoughtbot/hound/blob/d2f3933/app/models/pull_request.rb
4
+ class PseudoPullRequest
5
+ HOUND_CONFIG_FILE = '.hound.yml'
6
+
7
+ def initialize(files, options)
8
+ @files = files
9
+ @options = options
10
+ end
11
+
12
+ def pull_request_files
13
+ @files.map do |file|
14
+ build_commit_file(file)
15
+ end
16
+ end
17
+
18
+ def config
19
+ return unless @options.hound
20
+ File.read(HOUND_CONFIG_FILE)
21
+ rescue Errno::ENOENT
22
+ nil
23
+ end
24
+
25
+ private
26
+
27
+ def build_commit_file(file)
28
+ CommitFile.new(file, head_commit)
29
+ end
30
+
31
+ def head_commit
32
+ @head_commit ||= Commit.new(@options)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ module RuboCop
2
+ module Git
3
+ class PseudoResource
4
+ attr_reader :patch, :pwd, :file_relative_path
5
+
6
+ alias_method :filename, :file_relative_path
7
+
8
+ def initialize(file_relative_path, pwd = Dir.pwd)
9
+ @file_relative_path = file_relative_path
10
+ @pwd = pwd
11
+ @patch = ''
12
+ end
13
+
14
+ def absolute_path
15
+ filename
16
+ File.join(pwd, filename)
17
+ end
18
+
19
+ def status
20
+ 'modified'
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,70 @@
1
+ require 'shellwords'
2
+
3
+ module RuboCop
4
+ module Git
5
+ # ref. https://github.com/thoughtbot/hound/blob/d2f3933/app/services/build_runner.rb
6
+ class Runner
7
+ def run(options)
8
+ options = Options.new(options) unless options.is_a?(Options)
9
+
10
+ @options = options
11
+ @files = DiffParser.parse(git_diff(options))
12
+
13
+ display_violations($stdout)
14
+
15
+ exit(1) if violated?
16
+ end
17
+
18
+ private
19
+
20
+ def violations
21
+ @violations ||= style_checker.violations
22
+ end
23
+
24
+ def style_checker
25
+ StyleChecker.new(pull_request.pull_request_files,
26
+ @options.rubocop,
27
+ @options.config_file,
28
+ pull_request.config)
29
+ end
30
+
31
+ def pull_request
32
+ @pull_request ||= PseudoPullRequest.new(@files, @options)
33
+ end
34
+
35
+ def git_diff(options)
36
+ args = %w(diff --diff-filter=AMCR --find-renames --find-copies)
37
+
38
+ args << '--cached' if options.cached
39
+ args << options.commit_first.shellescape if options.commit_first
40
+ args << options.commit_last.shellescape if options.commit_last
41
+
42
+ `git #{args.join(' ')}`
43
+ end
44
+
45
+ def display_violations(io)
46
+ formatter = RuboCop::Formatter::ClangStyleFormatter.new(io)
47
+ formatter.started(nil)
48
+
49
+ violations.map do |violation|
50
+ offenses = violation.offenses
51
+ offenses = offenses.reject(&:disabled?) if offenses.first.respond_to?(:disabled?)
52
+ formatter.file_finished(
53
+ violation.filename,
54
+ offenses.compact.sort.freeze
55
+ )
56
+ end
57
+
58
+ formatter.finished(@files.map(&:filename).freeze)
59
+ end
60
+
61
+ def violated?
62
+ violations.any? do |violation|
63
+ offenses = violation.offenses
64
+ offenses = offenses.reject(&:disabled?) if offenses.first.respond_to?(:disabled?)
65
+ offenses && offenses.length > 0
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end