codeqa 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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