jruby-lint 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in jruby-lint.gemspec
4
+ gemspec
5
+
6
+ # Until a new childprocess gem incorporates my Dir.pwd fix
7
+ gem 'childprocess', :git => "https://github.com/nicksieger/childprocess.git"
8
+
9
+ gem 'guard'
10
+ gem 'guard-rspec'
11
+ gem 'growl'
12
+ gem 'rb-fsevent'
data/Guardfile ADDED
@@ -0,0 +1,12 @@
1
+ # Guardfile for JRuby-Lint
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb})
6
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
10
+ # Local Variables:
11
+ # mode: ruby
12
+ # End:
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ === 0.1 / 2011-05-18
2
+
3
+ * Birthday!
4
+
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # JRuby-Lint
2
+
3
+ See how ready your Ruby code is to run on JRuby.
4
+
5
+ JRuby-Lint is a simple tool that allows you to check your project code
6
+ and configuration for common gotchas and issues that might make it
7
+ difficult to run your code on JRuby.
8
+
9
+ ## Usage
10
+
11
+ JRuby-Lint requires JRuby to run. So, [install JRuby first][install]
12
+ if you already haven't, then `gem install jruby-lint`.
13
+
14
+ Then simply run `jrlint` in your project to receive a report of
15
+ places in your project where you should investigate further.
16
+
17
+ ## Checks
18
+
19
+ Here is a list of the current checks implemented:
20
+
21
+ - Report usage of ObjectSpace.each_object and ObjectSpace._id2ref
22
+ which are expensive and disabled by default
23
+ - Report usage of Thread.critical, which is discouraged in favor of a
24
+ plain Mutex.
25
+ - Report known gems and libraries that use C extensions and try to
26
+ provide known alternatives.
27
+ - Report usage of Kernel#fork (which does not work) and Kernel#exec
28
+ (which does not replace the current process).
29
+
30
+ ## TODO
31
+
32
+ Here is a list of checks and options we'd like to implement:
33
+
34
+ - Timeout.timeout
35
+ - Options to save report off to a file.
36
+ - Text, HTML formats
37
+ - Report on more threading and concurrency issues/antipatterns
38
+ - arr.each {|x| arr.delete(x) }
39
+ - Warn about `system("ruby ...")` etc. how they are run in-process by default
40
+ - Try to detect IO/File resource usage without blocks
41
+ - Check .gemspec files for extensions and extconf.rb for
42
+ #create_makefile and warn about compiliing C extensions
43
+ - Check whether Rails production.rb contains `config.threadsafe!`
44
+ - Detect ERB files and skip them, or...
45
+ - Detect ERB files and pre-process them to Ruby source with Erubis
46
+ - Detect Bundler gems that have a `platforms` qualifier and ignore
47
+ "platforms :ruby"
48
+ - Change to use jruby-parser
49
+ - 1.8/1.9 parser support/configuration without having to run JRuby
50
+ itself in the right mode
51
+ - Allow use of a comment marker to suppress individual checks
52
+
53
+ ### Further Down the Road
54
+
55
+ - Arbitrary method/AST search functionality
56
+ - Code rewriter: option to change code automatically where it's
57
+ feasible
58
+ - Revive or build an isit.jruby.org site for tracking
59
+ - Make JRuby-Lint submit results to tracking site based on lint passes
60
+ and/or test suite runs
61
+
62
+ [install]: http://jruby.org/getting-started
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'bundler/setup'
5
+ require 'rspec/core/rake_task'
6
+ ENV['RUN_ALL_SPECS'] = 'true' # Always run all specs from Rake
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task :default => [:update_fixtures, :spec]
10
+
11
+ require 'rake/clean'
12
+ require 'open-uri'
13
+ require 'net/https'
14
+
15
+ file "spec/fixtures/C-Extension-Alternatives.html" do |t|
16
+ require 'jruby/lint/gems'
17
+ cache = JRuby::Lint::Gems::Cache.new('spec/fixtures')
18
+ cache.fetch("C-Extension-Alternatives")
19
+ end
20
+
21
+ fixtures = ["spec/fixtures/C-Extension-Alternatives.html"]
22
+ task :update_fixtures => fixtures
23
+
24
+ CLOBBER.push *fixtures
data/bin/jrlint ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'jruby/lint/cli'
4
+
5
+ JRuby::Lint::CLI.new(ARGV).run
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "jruby/lint/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "jruby-lint"
7
+ s.version = JRuby::Lint::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Nick Sieger"]
10
+ s.email = ["nick@nicksieger.com"]
11
+ s.homepage = "https://github.com/jruby/jruby-lint"
12
+ s.summary = %q{See how ready your Ruby code is to run on JRuby.}
13
+ s.description = %q{This utility presents hints and suggestions to
14
+ give you an idea of potentially troublesome spots in your code and
15
+ dependencies that keep your code from running efficiently on JRuby.
16
+
17
+ Most pure Ruby code will run fine, but the two common areas that
18
+ trip people up are native extensions and threading}
19
+
20
+ s.rubyforge_project = "jruby-lint"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+
27
+ s.add_dependency "term-ansicolor"
28
+ s.add_dependency "jruby-openssl"
29
+ s.add_dependency "nokogiri", "= 1.5.0.beta.4"
30
+ s.add_development_dependency "rake"
31
+ s.add_development_dependency "rspec", ">= 2.5"
32
+ s.add_development_dependency "rspec-given"
33
+ s.add_development_dependency "aruba"
34
+ end
35
+
36
+ # Local Variables:
37
+ # mode: ruby
38
+ # End:
data/lib/jruby/lint.rb ADDED
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'jruby'
3
+ rescue LoadError
4
+ raise LoadError, 'JRuby-Lint must be run under JRuby'
5
+ end
6
+
7
+ module JRuby
8
+ module Lint
9
+ end
10
+ end
11
+
12
+ require 'jruby/lint/ast'
13
+ require 'jruby/lint/project'
14
+ require 'jruby/lint/finding'
15
+ require 'jruby/lint/libraries'
16
+ require 'jruby/lint/collectors'
17
+ require 'jruby/lint/checkers'
18
+ require 'jruby/lint/reporters'
19
+ require 'jruby/lint/version'
@@ -0,0 +1,56 @@
1
+ module JRuby::Lint
2
+ module AST
3
+ module MethodCalls
4
+ def visitCallNode(node)
5
+ visitMethodCallNode(node)
6
+ end
7
+
8
+ def visitFCallNode(node)
9
+ visitMethodCallNode(node)
10
+ end
11
+
12
+ def visitVCallNode(node)
13
+ visitMethodCallNode(node)
14
+ end
15
+
16
+ def visitAttrAssignNode(node)
17
+ visitMethodCallNode(node)
18
+ end
19
+ end
20
+
21
+ class Visitor
22
+ include Enumerable
23
+ include org.jruby.ast.visitor.NodeVisitor
24
+ attr_reader :ast
25
+
26
+ def initialize(ast)
27
+ @ast = ast
28
+ end
29
+
30
+ def each(&block)
31
+ @block = block
32
+ ast.accept(self)
33
+ ensure
34
+ @block = nil
35
+ end
36
+
37
+ alias each_node each
38
+ alias traverse each
39
+
40
+ def visit(method, node)
41
+ @block.call(node) if @block
42
+ node.child_nodes.each do |cn|
43
+ cn.accept(self) rescue nil
44
+ end
45
+ end
46
+
47
+ def method_missing(name, *args, &block)
48
+ if name.to_s =~ /^visit/
49
+ visit(name, *args)
50
+ else
51
+ super
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ # Any class with this module included in it will be loaded as a
2
+ # checker.
3
+ module JRuby::Lint
4
+ module Checker
5
+ def self.included(cls)
6
+ loaded_checkers << cls
7
+ end
8
+
9
+ def self.loaded_checkers
10
+ @checkers ||= []
11
+ end
12
+
13
+ attr_accessor :collector
14
+ end
15
+
16
+ module Checkers
17
+ end
18
+ end
19
+
20
+ require 'jruby/lint/checkers/fork_exec'
21
+ require 'jruby/lint/checkers/gem'
22
+ require 'jruby/lint/checkers/gemspec'
23
+ require 'jruby/lint/checkers/thread_critical'
24
+ require 'jruby/lint/checkers/object_space'
@@ -0,0 +1,47 @@
1
+ module JRuby::Lint
2
+ module Checkers
3
+ class ForkExec
4
+ include Checker
5
+
6
+ def visitCallNode(node)
7
+ if fork_exec?(node)
8
+ @call_node = node
9
+ child = node.child_nodes.first
10
+ if child && %w(COLON3NODE CONSTNODE).include?(child.node_type.to_s) && child.name == "Kernel"
11
+ add_finding(node)
12
+ end
13
+ proc { @call_node = nil }
14
+ end
15
+ end
16
+
17
+ def visitFCallNode(node)
18
+ add_finding(node) if fork_exec?(node)
19
+ end
20
+
21
+ def visitVCallNode(node)
22
+ if fork_exec?(node)
23
+ add_finding(node) unless @call_node
24
+ end
25
+ end
26
+
27
+ def fork_exec?(node)
28
+ node.name == "fork" || node.name == "exec"
29
+ end
30
+
31
+ def add_finding(node)
32
+ msg = nil
33
+ tags = [node.name]
34
+ case node.name
35
+ when 'fork'
36
+ msg = 'Kernel#fork is not implemented on JRuby.'
37
+ tags << :error
38
+ when 'exec'
39
+ msg = 'Kernel#exec does not replace the running JRuby process and may behave unexpectedly'
40
+ tags << :warning
41
+ end
42
+
43
+ collector.findings << Finding.new(msg, tags, node.position)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ module JRuby::Lint
2
+ module Checkers
3
+ module CheckGemNode
4
+ def self.add_wiki_link_finding(collector)
5
+ unless @added_wiki_link
6
+ collector.findings << Finding.new("For more on gem compatibility see http://wiki.jruby.org/C-Extension-Alternatives",
7
+ [:gems, :info]).tap do |f|
8
+ def f.to_s
9
+ message
10
+ end
11
+ end
12
+ @added_wiki_link = true
13
+ end
14
+ end
15
+
16
+ def gem_name(node)
17
+ first_arg = node.args_node.child_nodes[0]
18
+ if first_arg.node_type.to_s == "STRNODE"
19
+ first_arg.value.to_s
20
+ end
21
+ rescue
22
+ nil
23
+ end
24
+
25
+ def check_gem(collector, call_node)
26
+ @gems ||= collector.project.libraries.gems
27
+ gem_name = gem_name(call_node)
28
+ if instructions = @gems[gem_name]
29
+ CheckGemNode.add_wiki_link_finding(collector)
30
+ msg = "Found gem '#{gem_name}' which is reported to have some issues:\n#{instructions}"
31
+ collector.findings << Finding.new(msg, [:gems, :warning], call_node.position)
32
+ end
33
+ end
34
+ end
35
+
36
+ class Gem
37
+ include Checker
38
+ include CheckGemNode
39
+
40
+ def visitFCallNode(node)
41
+ check_gem(collector, node) if node.name == "gem"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,41 @@
1
+ module JRuby::Lint
2
+ module Checkers
3
+ class Gemspec
4
+ include Checker
5
+ include CheckGemNode
6
+
7
+ def visitCallNode(node)
8
+ # Gem::Specification.new do |s| =>
9
+ #
10
+ # CallNoArgBlockNode |new|
11
+ # Colon2ConstNode |Specification|
12
+ # IterNode
13
+ # DAsgnNode |s| &0 >0
14
+ # NilImplicitNode |nil|
15
+ # BlockNode
16
+ if node.name == "new" && # new
17
+ node.args_node.nil? && # no args
18
+ node.iter_node && # with a block
19
+ node.receiver_node.node_type.to_s == "COLON2NODE" && # :: - Colon2
20
+ node.receiver_node.name == "Specification" && # ::Specification
21
+ node.receiver_node.left_node.name == "Gem" # Gem::Specification
22
+ @gemspec_block_var = node.iter_node.var_node.name
23
+ return proc { @gemspec_block_var = nil }
24
+ end
25
+
26
+ # s.add_dependency "rdiscount" =>
27
+ #
28
+ # CallOneArgNode |add_dependency|
29
+ # DVarNode |s| &0 >0
30
+ # ArrayNode
31
+ # StrNode =="rdiscount"
32
+ if @gemspec_block_var &&
33
+ node.name == "add_dependency" &&
34
+ node.receiver_node.name == @gemspec_block_var
35
+ check_gem(collector, node)
36
+ end
37
+ rescue
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ module JRuby::Lint
2
+ module Checkers
3
+ class ObjectSpace
4
+ include Checker
5
+ METHODS = %w(each_object _id2ref)
6
+
7
+ def visitCallNode(node)
8
+ if METHODS.include?(node.name)
9
+ begin
10
+ next unless node.receiver_node.node_type.to_s == "CONSTNODE" && node.receiver_node.name == "ObjectSpace"
11
+ next if node.args_node && node.args_node.size == 1 &&
12
+ %w(Class Module).include?(node.args_node[0].name)
13
+ add_finding(collector, node)
14
+ rescue
15
+ end
16
+ end
17
+ end
18
+
19
+ def add_finding(collector, node)
20
+ collector.findings << Finding.new("Use of ObjectSpace is expensive and disabled by default. Use -X+O to enable.",
21
+ [:objectspace, :warning], node.position)
22
+ end
23
+ end
24
+ end
25
+ end