codeqa 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.codeqa.rb +22 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +93 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +10 -0
  8. data/.vimrc +1 -0
  9. data/CHANGELOG +8 -0
  10. data/Gemfile +18 -0
  11. data/Guardfile +21 -0
  12. data/LICENSE +22 -0
  13. data/README.md +97 -0
  14. data/Rakefile +19 -0
  15. data/bin/codeqa +75 -0
  16. data/codeqa.gemspec +21 -0
  17. data/config/default.rb +42 -0
  18. data/lib/codeqa/check_errors.rb +21 -0
  19. data/lib/codeqa/checker.rb +47 -0
  20. data/lib/codeqa/checkers/check_conflict.rb +28 -0
  21. data/lib/codeqa/checkers/check_erb.rb +48 -0
  22. data/lib/codeqa/checkers/check_erb_html.rb +42 -0
  23. data/lib/codeqa/checkers/check_linkto.rb +28 -0
  24. data/lib/codeqa/checkers/check_pry.rb +28 -0
  25. data/lib/codeqa/checkers/check_rspec_focus.rb +28 -0
  26. data/lib/codeqa/checkers/check_ruby_syntax.rb +25 -0
  27. data/lib/codeqa/checkers/check_strange_chars.rb +29 -0
  28. data/lib/codeqa/checkers/check_utf8_encoding.rb +22 -0
  29. data/lib/codeqa/checkers/check_yard.rb +55 -0
  30. data/lib/codeqa/checkers/pattern_checker.rb +26 -0
  31. data/lib/codeqa/checkers/rubocop_formatter.rb +29 -0
  32. data/lib/codeqa/checkers/rubocop_full.rb +53 -0
  33. data/lib/codeqa/checkers/rubocop_lint.rb +21 -0
  34. data/lib/codeqa/configuration.rb +94 -0
  35. data/lib/codeqa/fake_erb.rb +80 -0
  36. data/lib/codeqa/runner.rb +61 -0
  37. data/lib/codeqa/runner_decorator.rb +84 -0
  38. data/lib/codeqa/sourcefile.rb +50 -0
  39. data/lib/codeqa/version.rb +3 -0
  40. data/lib/codeqa.rb +78 -0
  41. data/lib/templates/pre-commit +50 -0
  42. data/spec/fixtures/html_error.html.erb +10 -0
  43. data/spec/fixtures/html_error.text.html +3 -0
  44. data/spec/fixtures/isolation/home/project/dir/.gitkeep +0 -0
  45. data/spec/fixtures/isolation/home/project/file.rb +1 -0
  46. data/spec/fixtures/isolation/home/project/ignored/some_file.txt +1 -0
  47. data/spec/fixtures/ruby.rb +3 -0
  48. data/spec/fixtures/ruby_error.rb +3 -0
  49. data/spec/lib/codeqa/checkers/check_conflict_spec.rb +30 -0
  50. data/spec/lib/codeqa/checkers/check_erb_html_spec.rb +72 -0
  51. data/spec/lib/codeqa/checkers/check_erb_spec.rb +31 -0
  52. data/spec/lib/codeqa/checkers/check_linkto_spec.rb +26 -0
  53. data/spec/lib/codeqa/checkers/check_pry_spec.rb +25 -0
  54. data/spec/lib/codeqa/checkers/check_rspec_focus_spec.rb +25 -0
  55. data/spec/lib/codeqa/checkers/check_ruby_syntax_spec.rb +26 -0
  56. data/spec/lib/codeqa/checkers/check_strange_chars_spec.rb +27 -0
  57. data/spec/lib/codeqa/checkers/check_utf8_encoding_spec.rb +26 -0
  58. data/spec/lib/codeqa/checkers/check_yard_spec.rb +21 -0
  59. data/spec/lib/codeqa/checkers/rubocop_formatter_spec.rb +5 -0
  60. data/spec/lib/codeqa/checkers/rubocop_full_spec.rb +5 -0
  61. data/spec/lib/codeqa/checkers/rubocop_lint_spec.rb +26 -0
  62. data/spec/lib/codeqa/configuration_spec.rb +52 -0
  63. data/spec/lib/codeqa/runner_decorator_spec.rb +19 -0
  64. data/spec/lib/codeqa/runner_spec.rb +5 -0
  65. data/spec/lib/codeqa/sourcefile_spec.rb +33 -0
  66. data/spec/lib/codeqa_spec.rb +52 -0
  67. data/spec/spec_helper.rb +56 -0
  68. data/spec/support/checker.rb +20 -0
  69. metadata +183 -0
@@ -0,0 +1,42 @@
1
+ require 'codeqa/fake_erb'
2
+ require 'open3'
3
+ module Codeqa
4
+ module Checkers
5
+ class CheckErbHtml < Checker
6
+ def self.check?(sourcefile)
7
+ sourcefile.html?
8
+ end
9
+
10
+ def name
11
+ 'erb html'
12
+ end
13
+
14
+ def hint
15
+ 'The html I see after removing the erb stuff is not valid (find the unclosed tags and attributes).'
16
+ end
17
+
18
+ def check
19
+ result = nil
20
+ with_existing_file(html) do |filename|
21
+ Open3.popen3("tidy -q -e -xml '#{filename}'") do |_in_stream, _out_stream, err_stream|
22
+ message = err_stream.read
23
+ result = message if message =~ /(Error:|missing trailing quote|end of file while parsing attributes)/m
24
+ end # IO.popen
25
+ end # Tempfile
26
+
27
+ return unless result
28
+ errors.add(nil, html)
29
+ errors.add(nil, result)
30
+ end
31
+
32
+ def html
33
+ @html ||= begin
34
+ html = FakeERB.new(sourcefile.content.gsub('<%=', '<%')).result
35
+ html = html.force_encoding('UTF-8') if html.respond_to?(:force_encoding)
36
+ html.gsub(%r{<script[ >].*?</script>|<style[ >].*?</style>}m,
37
+ '<!--removed script/style tag-->')
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,28 @@
1
+ require 'codeqa/checkers/pattern_checker'
2
+
3
+ module Codeqa
4
+ module Checkers
5
+ class CheckLinkto < PatternChecker
6
+ def name
7
+ 'link_to'
8
+ end
9
+
10
+ def hint
11
+ "<% link_to ... do ... %> detected add an '=' after the <%"
12
+ end
13
+
14
+ def self.check?(sourcefile)
15
+ sourcefile.erb?
16
+ end
17
+
18
+ private
19
+
20
+ def self.pattern
21
+ @pattern ||= /<% link_to.* do.*%>/
22
+ end
23
+ def error_msg(_line, line_number, _pos)
24
+ "old style block link_to in line #{line_number}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'codeqa/checkers/pattern_checker'
2
+
3
+ module Codeqa
4
+ module Checkers
5
+ class CheckPry < PatternChecker
6
+ def name
7
+ 'pry'
8
+ end
9
+
10
+ def hint
11
+ 'Leftover binding.pry found, please remove it.'
12
+ end
13
+
14
+ def self.check?(sourcefile)
15
+ sourcefile.ruby?
16
+ end
17
+
18
+ private
19
+
20
+ def self.pattern
21
+ @pattern ||= /binding\.pry/
22
+ end
23
+ def error_msg(_line, line_number, _pos)
24
+ "binding.pry in line #{line_number}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'codeqa/checkers/pattern_checker'
2
+
3
+ module Codeqa
4
+ module Checkers
5
+ class CheckRspecFocus < PatternChecker
6
+ def name
7
+ 'rspec-focus'
8
+ end
9
+
10
+ def hint
11
+ 'Leftover :focus in spec found, please remove it.'
12
+ end
13
+
14
+ def self.check?(sourcefile)
15
+ sourcefile.spec?
16
+ end
17
+
18
+ private
19
+
20
+ def self.pattern
21
+ @pattern ||= /:focus/
22
+ end
23
+ def error_msg(_line, line_number, _pos)
24
+ ":focus in line #{line_number}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ require 'erb'
2
+ module Codeqa
3
+ module Checkers
4
+ class CheckRubySyntax < Checker
5
+ def self.check?(sourcefile)
6
+ sourcefile.ruby?
7
+ end
8
+
9
+ def name
10
+ 'ruby syntax'
11
+ end
12
+
13
+ def hint
14
+ 'Ruby can not parse the file, please check it for syntax errors.'
15
+ end
16
+
17
+ def check
18
+ with_existing_file do |filename|
19
+ command = "/usr/bin/env ruby -c '#{filename}' 1>/dev/null 2>/dev/null"
20
+ errors.add(nil, 'Ruby syntax error') unless system(command)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ require 'codeqa/checkers/pattern_checker'
2
+
3
+ module Codeqa
4
+ module Checkers
5
+ class CheckStrangeChars < PatternChecker
6
+ def name
7
+ 'strange chars'
8
+ end
9
+
10
+ def hint
11
+ 'The file contains a tab or form feed. Remove them.'
12
+ end
13
+
14
+ def self.check?(sourcefile)
15
+ sourcefile.text?
16
+ end
17
+
18
+ private
19
+
20
+ def self.pattern
21
+ @pattern ||= /(\x09|\x0c)/
22
+ end
23
+ def error_msg(line, line_number, pos)
24
+ strangeness = (line.include?("\x09") ? 'TAB x09' : 'FORM FEED x0C')
25
+ "#{strangeness} at line #{line_number} column #{pos + 1}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ module Codeqa
2
+ module Checkers
3
+ class CheckUtf8Encoding < Checker
4
+ def self.check?(sourcefile)
5
+ sourcefile.text?
6
+ end
7
+
8
+ def name
9
+ 'utf8 encoding'
10
+ end
11
+
12
+ def hint
13
+ 'The file contains non utf8 characters. Find and remove them.'
14
+ end
15
+
16
+ def check
17
+ return if sourcefile.content.force_encoding('UTF-8').valid_encoding?
18
+ errors.add(nil, 'encoding error, not utf8')
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,55 @@
1
+ require 'stringio'
2
+
3
+ module Codeqa
4
+ module Checkers
5
+ class CheckYard < Checker
6
+ def self.check?(sourcefile)
7
+ sourcefile.ruby? && !(sourcefile.filename =~ /^(test|spec)/)
8
+ end
9
+ def self.available?
10
+ yard?
11
+ end
12
+
13
+ def self.io
14
+ @@io ||= StringIO.new
15
+ end
16
+
17
+ def name
18
+ 'yard'
19
+ end
20
+
21
+ def hint
22
+ 'Yard gives us some warnings on the file you can run <yardoc filename> to check yourself.'
23
+ end
24
+
25
+ def check
26
+ if self.class.yard?
27
+ ::YARD.parse_string(sourcefile.content) # let yard parse the file content
28
+ io.rewind # rewind the io
29
+ message = io.read
30
+ warnings = message.match(/\A\[warn\]: /)
31
+ errors.add(nil, message.gsub(/\(stdin\)/, sourcefile.filename)) if warnings
32
+ end
33
+ ensure
34
+ io.reopen # clear the message for the next file
35
+ end
36
+
37
+ private
38
+
39
+ def io
40
+ self.class.io
41
+ end
42
+
43
+ def self.yard?
44
+ @loaded ||= begin
45
+ require 'yard'
46
+ ::YARD::Logger.instance(io) # replace YARD logger with io
47
+ true
48
+ rescue LoadError
49
+ puts 'yard not installed'
50
+ false
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,26 @@
1
+ module Codeqa
2
+ module Checkers
3
+ class PatternChecker < Checker
4
+ def check
5
+ sourcefile.content.lines.each.with_index do |line, line_number|
6
+ pos = (line =~ pattern)
7
+ errors.add("#{line_number + 1},#{pos + 1}", error_msg(line, line_number + 1, pos)) if pos
8
+ end
9
+ end
10
+
11
+ def self.available?
12
+ respond_to?(:pattern)
13
+ end
14
+
15
+ private
16
+
17
+ def pattern
18
+ self.class.pattern
19
+ end
20
+
21
+ def error_msg(*_args)
22
+ raise 'not implemented'
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ require 'codeqa/checkers/rubocop_full'
2
+
3
+ module Codeqa
4
+ module Checkers
5
+ class RubocopFormatter < Rubocop
6
+ def name
7
+ 'rubocop formatter'
8
+ end
9
+
10
+ def hint
11
+ <<-EOF
12
+ Rubocop reformatted your code.
13
+ Check what it has done and add the changes to git's index
14
+ EOF
15
+ end
16
+
17
+ def after_check
18
+ # add changes to the git index
19
+ # `git add #{sourcefile.filename}`
20
+ end
21
+
22
+ private
23
+
24
+ def config_args
25
+ %w(--auto-correct --only ) << Codeqa.configuration.rubocop_formatter_cops.to_a.join(',')
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ module Codeqa
2
+ module Checkers
3
+ class Rubocop < Checker
4
+ def self.check?(sourcefile)
5
+ sourcefile.ruby?
6
+ end
7
+
8
+ def self.available?
9
+ rubocop?
10
+ end
11
+
12
+ def name
13
+ 'rubocop'
14
+ end
15
+
16
+ def hint
17
+ 'Rubocop does not like your syntax, please fix your code.'
18
+ end
19
+
20
+ def check
21
+ return unless self.class.rubocop?
22
+ with_existing_file do |filename|
23
+ args = config_args << filename
24
+ success, captured = capture do
25
+ if defined?(RuboCop) # its RuboCop since 0.24
26
+ ::RuboCop::CLI.new.run(default_args + args) == 0
27
+ else
28
+ ::Rubocop::CLI.new.run(default_args + args) == 0
29
+ end
30
+ end
31
+ errors.add(nil, captured) unless success
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def config_args
38
+ %w(--auto-correct --fail-level warning)
39
+ end
40
+
41
+ def default_args
42
+ %w(--display-cop-names --format emacs)
43
+ end
44
+
45
+ def self.rubocop?
46
+ @loaded ||= begin
47
+ require 'rubocop'
48
+ true
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,21 @@
1
+ require 'codeqa/checkers/rubocop_full'
2
+
3
+ module Codeqa
4
+ module Checkers
5
+ class RubocopLint < Rubocop
6
+ def name
7
+ 'rubocop lint'
8
+ end
9
+
10
+ def hint
11
+ 'Rubocop found syntax errors, please fix your code.'
12
+ end
13
+
14
+ private
15
+
16
+ def config_args
17
+ %w(--lint --fail-level error)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,94 @@
1
+ require 'set'
2
+ module Codeqa
3
+ class Configuration
4
+ # the default config file will setup all variables to some sane defaults
5
+ attr_accessor :erb_engine
6
+ attr_reader :excludes,
7
+ :enabled_checker,
8
+ :rubocop_formatter_cops
9
+
10
+ def excludes=(val)
11
+ @excludes = Set[*val]
12
+ end
13
+
14
+ def enabled_checker=(val)
15
+ @enabled_checker = Set[*val]
16
+ end
17
+
18
+ def rubocop_formatter_cops=(val)
19
+ @rubocop_formatter_cops = Set[*val]
20
+ end
21
+
22
+ def default_config_path
23
+ Codeqa.root.join('config', 'default')
24
+ end
25
+
26
+ def home_config_path
27
+ home_dir_config = File.join(home_dir, DOTFILE)
28
+ return home_dir_config if File.exist? home_dir_config
29
+ false
30
+ end
31
+
32
+ def project_config_path
33
+ project_root_config = File.join(project_root, DOTFILE)
34
+ return project_root_config if File.exist? project_root_config
35
+ false
36
+ end
37
+
38
+ #
39
+ # tests a given filepath if it should be excluded
40
+ # @param file File.join compatable filepath
41
+ #
42
+ # @return [Boolean]
43
+ def excluded?(file)
44
+ file = File.join(Dir.pwd, file) unless file.start_with?('/')
45
+ Codeqa.configuration.excludes.any?{ |pattern| match_path?(pattern, file) }
46
+ end
47
+
48
+ private
49
+
50
+ DOTFILE = '.codeqa.rb'
51
+
52
+ def home_dir
53
+ @home_dir ||= Dir.home
54
+ end
55
+
56
+ def project_root
57
+ @project_root ||= git_root_till_home
58
+ end
59
+
60
+ # ascend from the current dir till I find a .git folder or reach home_dir
61
+ def git_root_till_home
62
+ Pathname.new(Dir.pwd).ascend do |dir_pathname|
63
+ return dir_pathname if File.directory?("#{dir_pathname}/.git")
64
+ return nil if dir_pathname.to_s == home_dir
65
+ end
66
+ end
67
+
68
+ def match_path?(pattern, path)
69
+ case pattern
70
+ when String
71
+ basename = File.basename(path)
72
+ pattern = File.join(project_root, pattern) unless pattern.start_with?('/')
73
+ path == pattern || basename == pattern || File.fnmatch(pattern, path)
74
+ when Regexp
75
+ path =~ pattern
76
+ end
77
+ end
78
+ end
79
+
80
+ class << self
81
+ def configuration
82
+ @configuration ||= Configuration.new
83
+ end
84
+
85
+ def configure
86
+ yield(configuration) if block_given?
87
+ Codeqa.register_checkers
88
+ end
89
+ end
90
+ end
91
+
92
+ require Codeqa.configuration.default_config_path
93
+ require Codeqa.configuration.home_config_path if Codeqa.configuration.home_config_path
94
+ require Codeqa.configuration.project_config_path if Codeqa.configuration.project_config_path
@@ -0,0 +1,80 @@
1
+ require 'erb'
2
+ module Codeqa
3
+ # copied from ERB
4
+ # throws away all the erb stuff so only html remains
5
+ # rubocop:disable MethodLength, LineLength, CyclomaticComplexity, BlockNesting
6
+ class FakeERB < ERB
7
+ def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout', compiler_class=FakeERB::Compiler)
8
+ @safe_level = safe_level
9
+ compiler = compiler_class.new(trim_mode)
10
+ set_eoutvar(compiler, eoutvar)
11
+ @src = compiler.compile(str)
12
+ @filename = nil
13
+ end
14
+
15
+ class Compiler < ERB::Compiler # :nodoc:
16
+ def compile(s)
17
+ out = Buffer.new(self)
18
+
19
+ content = ''
20
+ scanner = make_scanner(s)
21
+ scanner.scan do |token|
22
+ next if token.nil?
23
+ next if token == ''
24
+ if scanner.stag.nil?
25
+ case token
26
+ when PercentLine
27
+ out.push("#{@put_cmd} #{content_dump(content)}") if content.size > 0
28
+ content = ''
29
+ out.push(token.to_s)
30
+ out.cr
31
+ when :cr
32
+ out.cr
33
+ when '<%', '<%=', '<%#'
34
+ scanner.stag = token
35
+ out.push("#{@put_cmd} #{content_dump(content)}") if content.size > 0
36
+ content = ''
37
+ when "\n"
38
+ content << "\n"
39
+ out.push("#{@put_cmd} #{content_dump(content)}")
40
+ content = ''
41
+ when '<%%'
42
+ content << '<%'
43
+ else
44
+ content << token
45
+ end
46
+ else
47
+ case token
48
+ when '%>'
49
+ case scanner.stag
50
+ when '<%'
51
+ if content[-1] == "\n"
52
+ content.chop!
53
+ # out.push(content)
54
+ out.cr
55
+ else
56
+ # out.push(content)
57
+ end
58
+ when '<%='
59
+ # out.push("#{@insert_cmd}((#{content}).to_s)")
60
+ when '<%#'
61
+ # out.push("# #{content_dump(content)}")
62
+ end
63
+ scanner.stag = nil
64
+ content = ''
65
+ when '%%>'
66
+ content << '%>'
67
+ else
68
+ content << token
69
+ end
70
+ end
71
+ end
72
+ # puts "pushed2 #{content}"
73
+ out.push("#{@put_cmd} #{content_dump(content)}") if content.size > 0
74
+ out.close
75
+ out.script
76
+ end
77
+ end
78
+ end
79
+ # rubocop:enable MethodLength, LineLength, CyclomaticComplexity, BlockNesting
80
+ end
@@ -0,0 +1,61 @@
1
+ require 'set'
2
+
3
+ module Codeqa
4
+ class Runner
5
+ class << self
6
+ def registered_checkers
7
+ @@registered_checkers
8
+ end
9
+
10
+ def reset_checkers
11
+ @@registered_checkers = Set.new
12
+ end
13
+
14
+ def register_checker(checker_class)
15
+ @@registered_checkers << checker_class
16
+ end
17
+ end
18
+ @@registered_checkers = reset_checkers
19
+
20
+ # run the checks on source
21
+ def self.run(sourcefile)
22
+ runner = new(sourcefile)
23
+ runner.run
24
+ runner
25
+ end
26
+
27
+ def initialize(sourcefile)
28
+ @sourcefile = sourcefile
29
+ @results = []
30
+ end
31
+ attr_reader :sourcefile
32
+
33
+ def run
34
+ return @results unless @results.empty?
35
+ @results = @@registered_checkers.map do |checker_klass|
36
+ next unless checker_klass.check?(sourcefile)
37
+ checker = checker_klass.new(sourcefile)
38
+
39
+ checker.before_check if checker.respond_to?(:before_check)
40
+ checker.check
41
+ checker.after_check if checker.respond_to?(:after_check)
42
+ checker
43
+ end.compact
44
+ end
45
+
46
+ # the results (checker instances of the run)
47
+ attr_reader :results
48
+
49
+ def failures
50
+ @failures ||= @results.reject{ |checker| checker.success? }
51
+ end
52
+
53
+ def success?
54
+ failures.empty?
55
+ end
56
+
57
+ def display_result(options={})
58
+ RunnerDecorator.new(self, options)
59
+ end
60
+ end
61
+ end