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
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