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