jruby-lint 0.1

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 (40) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +12 -0
  3. data/Guardfile +12 -0
  4. data/History.txt +4 -0
  5. data/README.md +62 -0
  6. data/Rakefile +24 -0
  7. data/bin/jrlint +5 -0
  8. data/jruby-lint.gemspec +38 -0
  9. data/lib/jruby/lint.rb +19 -0
  10. data/lib/jruby/lint/ast.rb +56 -0
  11. data/lib/jruby/lint/checkers.rb +24 -0
  12. data/lib/jruby/lint/checkers/fork_exec.rb +47 -0
  13. data/lib/jruby/lint/checkers/gem.rb +45 -0
  14. data/lib/jruby/lint/checkers/gemspec.rb +41 -0
  15. data/lib/jruby/lint/checkers/object_space.rb +25 -0
  16. data/lib/jruby/lint/checkers/thread_critical.rb +26 -0
  17. data/lib/jruby/lint/cli.rb +61 -0
  18. data/lib/jruby/lint/collectors.rb +75 -0
  19. data/lib/jruby/lint/collectors/bundler.rb +9 -0
  20. data/lib/jruby/lint/collectors/gemspec.rb +9 -0
  21. data/lib/jruby/lint/collectors/rake.rb +9 -0
  22. data/lib/jruby/lint/collectors/ruby.rb +14 -0
  23. data/lib/jruby/lint/finding.rb +15 -0
  24. data/lib/jruby/lint/github.crt +76 -0
  25. data/lib/jruby/lint/libraries.rb +107 -0
  26. data/lib/jruby/lint/project.rb +56 -0
  27. data/lib/jruby/lint/reporters.rb +35 -0
  28. data/lib/jruby/lint/version.rb +5 -0
  29. data/spec/fixtures/C-Extension-Alternatives.html +557 -0
  30. data/spec/jruby/lint/ast_spec.rb +23 -0
  31. data/spec/jruby/lint/checkers_spec.rb +135 -0
  32. data/spec/jruby/lint/cli_spec.rb +66 -0
  33. data/spec/jruby/lint/collectors_spec.rb +34 -0
  34. data/spec/jruby/lint/finding_spec.rb +31 -0
  35. data/spec/jruby/lint/libraries_spec.rb +56 -0
  36. data/spec/jruby/lint/project_spec.rb +77 -0
  37. data/spec/jruby/lint/reporters_spec.rb +42 -0
  38. data/spec/jruby/lint/version_spec.rb +6 -0
  39. data/spec/spec_helper.rb +30 -0
  40. metadata +194 -0
@@ -0,0 +1,26 @@
1
+ module JRuby::Lint
2
+ module Checkers
3
+ class ThreadCritical
4
+ include Checker
5
+
6
+ METHODS = %w(critical critical=)
7
+
8
+ def visitCallNode(node)
9
+ if METHODS.include?(node.name)
10
+ begin
11
+ if node.receiver_node.node_type.to_s == "CONSTNODE" && node.receiver_node.name == "Thread"
12
+ add_finding(collector, node)
13
+ end
14
+ rescue
15
+ end
16
+ end
17
+ end
18
+ alias visitAttrAssignNode visitCallNode
19
+
20
+ def add_finding(collector, node)
21
+ collector.findings << Finding.new("Use of Thread.critical is discouraged. Use a Mutex instead.",
22
+ [:threads, :warning], node.position)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,61 @@
1
+ module JRuby
2
+ module Lint
3
+ class CLI
4
+ def initialize(args)
5
+ process_options(args)
6
+ end
7
+
8
+ def process_options(args)
9
+ require 'optparse'
10
+ require 'ostruct'
11
+ @options = OpenStruct.new
12
+ OptionParser.new do |opts|
13
+ opts.banner = "Usage: jrlint [options] [files]"
14
+ opts.separator ""
15
+ opts.separator "Options:"
16
+
17
+ opts.on('-C', '--chdir DIRECTORY', "Change working directory") do |v|
18
+ Dir.chdir(v)
19
+ end
20
+
21
+ opts.on("-e", '--eval SCRIPT', "Lint an inline script") do |v|
22
+ @options.eval ||= []
23
+ @options.eval << v
24
+ end
25
+
26
+ opts.on_tail("-v", "--version", "Print version and exit") do
27
+ require 'jruby/lint/version'
28
+ puts "JRuby-Lint version #{VERSION}"
29
+ exit
30
+ end
31
+
32
+ opts.on_tail("-h", "--help", "This message") do
33
+ puts opts
34
+ exit
35
+ end
36
+ end.parse!(args)
37
+
38
+ @options.files = args.empty? ? nil : args
39
+ end
40
+
41
+ def run
42
+ require 'jruby/lint'
43
+ require 'benchmark'
44
+ project = JRuby::Lint::Project.new(@options)
45
+
46
+ puts "JRuby-Lint version #{JRuby::Lint::VERSION}"
47
+ time = Benchmark.realtime { project.run }
48
+ term = @options.eval ? 'expression' : 'file'
49
+ puts "Processed #{project.files.size} #{term}#{project.files.size == 1 ? '' : 's'} in #{'%0.02f' % time} seconds"
50
+
51
+ if project.findings.empty?
52
+ puts "OK"
53
+ exit
54
+ else
55
+ puts "Found #{project.findings.size} items"
56
+ exit 1
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,75 @@
1
+ module JRuby::Lint
2
+ class Collector
3
+ attr_accessor :checkers, :findings, :project, :contents, :file
4
+
5
+ def initialize(project = nil, file = nil)
6
+ @checkers = Checker.loaded_checkers.map(&:new)
7
+ @checkers.each {|c| c.collector = self }
8
+ @findings = []
9
+ @project, @file = project, file || '<inline-script>'
10
+ end
11
+
12
+ class CheckersVisitor < AST::Visitor
13
+ attr_reader :checkers
14
+
15
+ def initialize(ast, checkers)
16
+ super(ast)
17
+ @checkers = checkers
18
+ end
19
+
20
+ def visit(method, node)
21
+ after_hooks = []
22
+ checkers.each do |ch|
23
+ begin
24
+ if ch.respond_to?(method)
25
+ res = ch.send(method, node)
26
+ after_hooks << res if res.respond_to?(:call)
27
+ end
28
+ rescue => e
29
+ ch.collector.findings << Finding.new("Exception while traversing: #{e.message}",
30
+ [:internal, :debug], node.position)
31
+ end
32
+ end
33
+ super
34
+ ensure
35
+ begin
36
+ after_hooks.each {|h| h.call }
37
+ rescue
38
+ end
39
+ end
40
+ end
41
+
42
+ def run
43
+ begin
44
+ CheckersVisitor.new(ast, checkers).traverse
45
+ rescue SyntaxError => e
46
+ file, line, message = e.message.split(/:\s*/, 3)
47
+ findings << Finding.new(message, [:syntax, :error], file, line)
48
+ end
49
+ end
50
+
51
+ def ast
52
+ @ast ||= JRuby.parse(contents, file, true)
53
+ end
54
+
55
+ def contents
56
+ @contents || File.read(@file)
57
+ end
58
+
59
+ def self.inherited(base)
60
+ self.all << base
61
+ end
62
+
63
+ def self.all
64
+ @collectors ||= []
65
+ end
66
+ end
67
+
68
+ module Collectors
69
+ end
70
+ end
71
+
72
+ require 'jruby/lint/collectors/ruby'
73
+ require 'jruby/lint/collectors/bundler'
74
+ require 'jruby/lint/collectors/rake'
75
+ require 'jruby/lint/collectors/gemspec'
@@ -0,0 +1,9 @@
1
+ module JRuby::Lint
2
+ module Collectors
3
+ class Bundler < Collector
4
+ def self.detect?(f)
5
+ File.basename(f) == 'Gemfile'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module JRuby::Lint
2
+ module Collectors
3
+ class Gemspec < Collector
4
+ def self.detect?(f)
5
+ File.extname(f) == '.gemspec'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module JRuby::Lint
2
+ module Collectors
3
+ class Rake < Collector
4
+ def self.detect?(f)
5
+ File.basename(f) == 'Rakefile' || File.extname(f) == '.rake'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ module JRuby::Lint
2
+ module Collectors
3
+ class Ruby < Collector
4
+ def initialize(project, file, contents = nil)
5
+ super(project, file)
6
+ @contents = contents
7
+ end
8
+
9
+ def self.detect?(f)
10
+ File.extname(f) == '.rb'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module JRuby::Lint
2
+ class Finding < Struct.new(:message, :tags, :file, :line)
3
+ def initialize(*args)
4
+ args[1].map! {|x| x.to_s } if args.size > 1
5
+ if args.size > 2 && args[2].respond_to?(:file) && args[2].respond_to?(:line)
6
+ args = [args[0], args[1], args[2].file, (args[2].line + 1)]
7
+ end
8
+ super
9
+ end
10
+
11
+ def to_s
12
+ "#{file}:#{line}: [#{tags.join(', ')}] #{message}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,76 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
3
+ ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
4
+ RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
5
+ MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
6
+ QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
7
+ b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
8
+ b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
9
+ YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
10
+ AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
11
+ KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
12
+ VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
13
+ SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
14
+ cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
15
+ 6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
16
+ MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
17
+ kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
18
+ BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
19
+ BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
20
+ c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
21
+ AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
22
+ BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
23
+ OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
24
+ A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
25
+ 0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
26
+ RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
27
+ qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
28
+ U+4=
29
+ -----END CERTIFICATE-----
30
+ -----BEGIN CERTIFICATE-----
31
+ MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh
32
+ bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu
33
+ Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g
34
+ QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe
35
+ BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoX
36
+ DTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBE
37
+ YWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0
38
+ aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC
39
+ ggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
40
+ 2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+q
41
+ N1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiO
42
+ r18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN
43
+ f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEH
44
+ U1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHU
45
+ TBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMb
46
+ VmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwg
47
+ SW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlv
48
+ biBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEg
49
+ MB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUw
50
+ AwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdv
51
+ ZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMu
52
+ Z29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUd
53
+ IAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNv
54
+ bS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1
55
+ QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4O
56
+ WBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0Vmsf
57
+ SxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==
58
+ -----END CERTIFICATE-----
59
+ -----BEGIN CERTIFICATE-----
60
+ MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
61
+ IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
62
+ BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
63
+ aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
64
+ 9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
65
+ NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
66
+ azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
67
+ YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
68
+ Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
69
+ cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
70
+ dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
71
+ WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
72
+ v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
73
+ UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
74
+ IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
75
+ W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
76
+ -----END CERTIFICATE-----
@@ -0,0 +1,107 @@
1
+ require 'net/https'
2
+ require 'nokogiri'
3
+ require 'tempfile'
4
+ require 'fileutils'
5
+
6
+ module JRuby::Lint
7
+ class Libraries
8
+ class Cache
9
+ def initialize(cache_dir = nil)
10
+ @cache_dir = cache_dir || ENV['JRUBY_LINT_CACHE'] ||
11
+ (defined?(Gem.user_dir) && File.join(Gem.user_dir, 'lint')) || Dir::tmpdir
12
+ FileUtils.mkdir_p(@cache_dir) unless File.directory?(@cache_dir)
13
+ end
14
+
15
+ def fetch(name)
16
+ filename = filename_for(name)
17
+ if File.file?(filename) && !stale?(filename)
18
+ File.read(filename)
19
+ else
20
+ read_from_wiki(name, filename)
21
+ end
22
+ end
23
+
24
+ def store(name, content)
25
+ File.open(filename_for(name), "w") {|f| f << content }
26
+ end
27
+
28
+ def filename_for(name)
29
+ name = File.basename(name)
30
+ File.join(@cache_dir, File.extname(name).empty? ? "#{name}.html" : name)
31
+ end
32
+
33
+ def stale?(filename)
34
+ File.mtime(filename) < Time.now - 24 * 60 * 60
35
+ end
36
+
37
+ def read_from_wiki(name, filename)
38
+ content = nil
39
+ uri = Net::HTTP.start('wiki.jruby.org', 80) do |http|
40
+ URI.parse http.head(name =~ %r{^/} ? name : "/#{name}")['Location']
41
+ end
42
+ if uri.host == "github.com"
43
+ Net::HTTP.new(uri.host, uri.port).tap do |http|
44
+ if uri.scheme == "https"
45
+ http.use_ssl = true
46
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
47
+ # gd_bundle.crt from https://certs.godaddy.com/anonymous/repository.seam
48
+ http.ca_file = File.expand_path('../github.crt', __FILE__)
49
+ end
50
+ http.start do
51
+ response = http.get(uri.path)
52
+ content = response.body
53
+ File.open(filename, "w") do |f|
54
+ f << content
55
+ end
56
+ end
57
+ end
58
+ end
59
+ raise "Unknown location '#{uri}' for page '#{name}'" unless content
60
+ content
61
+ rescue => e
62
+ raise "Error while reading from wiki: #{e.message}\nPlease try again later."
63
+ end
64
+ end
65
+
66
+ class CExtensions
67
+ attr_reader :gems
68
+
69
+ def initialize(cache)
70
+ @cache = cache
71
+ end
72
+
73
+ def load
74
+ @gems = {}
75
+ content = @cache.fetch('C-Extension-Alternatives')
76
+ doc = Nokogiri::HTML(content)
77
+ doc.css('#wiki-body ul li').each do |li|
78
+ key, message = li.text.split(/[ -]+/, 2)
79
+ @gems[key.downcase] = message
80
+ end
81
+ rescue => e
82
+ @error = "Unable to load C Extension alternatives list: #{e.message}"
83
+ end
84
+ end
85
+
86
+ SOURCES = [CExtensions]
87
+
88
+ attr_reader :gems
89
+
90
+ def initialize(cache)
91
+ @sources = SOURCES.map {|s| s.new(cache) }
92
+ end
93
+
94
+ def load
95
+ @gems = {}.tap do |gems|
96
+ @sources.each do |s|
97
+ s.load
98
+ gems.update(s.gems)
99
+ end
100
+ end
101
+ end
102
+
103
+ def gems
104
+ @gems ||= load
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,56 @@
1
+ require 'set'
2
+ require 'ostruct'
3
+
4
+ module JRuby::Lint
5
+ class Project
6
+ DEFAULT_TAGS = %w(error warning info)
7
+
8
+ attr_reader :collectors, :reporters, :findings, :files, :tags, :libraries
9
+
10
+ def initialize(options = OpenStruct.new)
11
+ @tags = DEFAULT_TAGS
12
+ @collectors = []
13
+ @files = Set.new
14
+
15
+ if options.eval
16
+ options.eval.each {|e| @collectors << JRuby::Lint::Collectors::Ruby.new(self, '-e', e) }
17
+ @files += @collectors
18
+ end
19
+
20
+ @sources = options.files || (options.eval ? [] : Dir['./**/*'])
21
+ load_collectors
22
+ load_reporters
23
+ load_libraries
24
+ end
25
+
26
+ def run
27
+ @findings = []
28
+ collectors.each do |c|
29
+ c.run
30
+ reporters.each {|r| r.report(c.findings)}
31
+ @findings += c.findings
32
+ end
33
+ end
34
+
35
+ private
36
+ def load_collectors
37
+ @sources.each do |f|
38
+ next unless File.file?(f)
39
+ Collector.all.each do |c|
40
+ if c.detect?(f)
41
+ @collectors << c.new(self, f)
42
+ @files << f
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def load_reporters
49
+ @reporters = [(STDOUT.tty? ? Reporters::ANSIColor : Reporters::Text).new(self, STDOUT)]
50
+ end
51
+
52
+ def load_libraries
53
+ @libraries = Libraries.new(Libraries::Cache.new)
54
+ end
55
+ end
56
+ end