jruby-lint 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +12 -0
- data/Guardfile +12 -0
- data/History.txt +4 -0
- data/README.md +62 -0
- data/Rakefile +24 -0
- data/bin/jrlint +5 -0
- data/jruby-lint.gemspec +38 -0
- data/lib/jruby/lint.rb +19 -0
- data/lib/jruby/lint/ast.rb +56 -0
- data/lib/jruby/lint/checkers.rb +24 -0
- data/lib/jruby/lint/checkers/fork_exec.rb +47 -0
- data/lib/jruby/lint/checkers/gem.rb +45 -0
- data/lib/jruby/lint/checkers/gemspec.rb +41 -0
- data/lib/jruby/lint/checkers/object_space.rb +25 -0
- data/lib/jruby/lint/checkers/thread_critical.rb +26 -0
- data/lib/jruby/lint/cli.rb +61 -0
- data/lib/jruby/lint/collectors.rb +75 -0
- data/lib/jruby/lint/collectors/bundler.rb +9 -0
- data/lib/jruby/lint/collectors/gemspec.rb +9 -0
- data/lib/jruby/lint/collectors/rake.rb +9 -0
- data/lib/jruby/lint/collectors/ruby.rb +14 -0
- data/lib/jruby/lint/finding.rb +15 -0
- data/lib/jruby/lint/github.crt +76 -0
- data/lib/jruby/lint/libraries.rb +107 -0
- data/lib/jruby/lint/project.rb +56 -0
- data/lib/jruby/lint/reporters.rb +35 -0
- data/lib/jruby/lint/version.rb +5 -0
- data/spec/fixtures/C-Extension-Alternatives.html +557 -0
- data/spec/jruby/lint/ast_spec.rb +23 -0
- data/spec/jruby/lint/checkers_spec.rb +135 -0
- data/spec/jruby/lint/cli_spec.rb +66 -0
- data/spec/jruby/lint/collectors_spec.rb +34 -0
- data/spec/jruby/lint/finding_spec.rb +31 -0
- data/spec/jruby/lint/libraries_spec.rb +56 -0
- data/spec/jruby/lint/project_spec.rb +77 -0
- data/spec/jruby/lint/reporters_spec.rb +42 -0
- data/spec/jruby/lint/version_spec.rb +6 -0
- data/spec/spec_helper.rb +30 -0
- metadata +194 -0
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
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
data/jruby-lint.gemspec
ADDED
@@ -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
|