jruby-lint 0.4.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -1
- data/README.md +2 -8
- data/jruby-lint.gemspec +2 -3
- data/lib/jruby/lint/ast.rb +1 -1
- data/lib/jruby/lint/checkers.rb +18 -1
- data/lib/jruby/lint/checkers/fork_exec.rb +22 -39
- data/lib/jruby/lint/checkers/gem.rb +44 -30
- data/lib/jruby/lint/checkers/gemspec.rb +4 -4
- data/lib/jruby/lint/checkers/nonatomic.rb +38 -8
- data/lib/jruby/lint/checkers/object_space.rb +8 -6
- data/lib/jruby/lint/checkers/system.rb +4 -4
- data/lib/jruby/lint/checkers/thread_critical.rb +4 -4
- data/lib/jruby/lint/cli.rb +8 -1
- data/lib/jruby/lint/collectors.rb +18 -7
- data/lib/jruby/lint/finding.rb +1 -1
- data/lib/jruby/lint/libraries.rb +21 -30
- data/lib/jruby/lint/project.rb +3 -3
- data/lib/jruby/lint/reporters/html.rb +4 -3
- data/lib/jruby/lint/reporters/jruby-lint.html.erb +10 -1
- data/lib/jruby/lint/reporters/text.rb +5 -4
- data/lib/jruby/lint/version.rb +1 -1
- data/spec/fixtures/C-Extension-Alternatives.md +81 -0
- data/spec/jruby/lint/ast_spec.rb +6 -6
- data/spec/jruby/lint/checkers_spec.rb +91 -62
- data/spec/jruby/lint/collectors_spec.rb +3 -9
- data/spec/jruby/lint/finding_spec.rb +10 -10
- data/spec/jruby/lint/libraries_spec.rb +13 -13
- data/spec/jruby/lint/project_spec.rb +34 -40
- data/spec/jruby/lint/reporters_spec.rb +22 -13
- data/spec/jruby/lint/version_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +30 -96
- data/lib/jruby/lint/checkers/timeout.rb +0 -21
- data/lib/jruby/lint/github.crt +0 -23
- data/spec/fixtures/C-Extension-Alternatives.html +0 -339
- data/spec/jruby/lint/cli_spec.rb +0 -73
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8acd78ec48b4d72064f74789407130256498dc68b6098c110a63cb09c49a157f
|
4
|
+
data.tar.gz: 732d5f3eca8aeb36fc73f9a36b86aa2f0491908d2e11044f9dc7eae28b8ae76d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1a71a1e831b137e562bb5153a0734a079756c35656c5ef8dd998171f5d7b6fe7f8ea366a2bb4ef0c32a3a00bf698fcee7f1847bd65bafe1344e2b7fe9591b338
|
7
|
+
data.tar.gz: 18b7f7e00ead7675ac6bbe625bca3c3a825f3ac739c1a674acf568157e2a13dd0313b22b1dedb7418d59023c7858c4c1789a589607ce7f45208a815a2b1543d1
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -23,11 +23,8 @@ Here is a list of the current checks implemented:
|
|
23
23
|
- Report usage of Thread.critical, which is discouraged in favor of a
|
24
24
|
plain Mutex.
|
25
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)
|
28
|
-
(which does not replace the current process).
|
29
|
-
- Report usage of Timeout::timeout which, when used excessively tend
|
30
|
-
to be slow and expensive because of native threads
|
26
|
+
provide known alternatives (Live data retrieved from https://github.com/jruby/jruby/wiki/C-Extension-Alternatives).
|
27
|
+
- Report usage of Kernel#fork (which does not work).
|
31
28
|
- Report behavior difference when using system('ruby'), which launches
|
32
29
|
the command in-process in a new copy of the interpreter for speed
|
33
30
|
|
@@ -40,7 +37,6 @@ to generate an html report with the results.
|
|
40
37
|
|
41
38
|
Here is a list of checks and options we'd like to implement:
|
42
39
|
|
43
|
-
- Add in check for `` to make sure not execing ruby ...
|
44
40
|
- Report on more threading and concurrency issues/antipatterns
|
45
41
|
- arr.each {|x| arr.delete(x) }
|
46
42
|
- Try to detect IO/File resource usage without blocks
|
@@ -52,8 +48,6 @@ Here is a list of checks and options we'd like to implement:
|
|
52
48
|
- Detect Bundler gems that have a `platforms` qualifier and ignore
|
53
49
|
"platforms :ruby"
|
54
50
|
- Change to use jruby-parser
|
55
|
-
- 1.8/1.9 parser support/configuration without having to run JRuby
|
56
|
-
itself in the right mode
|
57
51
|
- Allow use of a comment marker to suppress individual checks
|
58
52
|
|
59
53
|
### Further Down the Road
|
data/jruby-lint.gemspec
CHANGED
@@ -6,9 +6,10 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "jruby-lint"
|
7
7
|
s.version = JRuby::Lint::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
|
+
s.required_ruby_version = '>= 2.5.3'
|
9
10
|
s.authors = ["Nick Sieger"]
|
10
11
|
s.email = ["nick@nicksieger.com"]
|
11
|
-
s.licenses = ["EPL
|
12
|
+
s.licenses = ["EPL-1.0", "GPL-2.0", "LGPL-2.1"]
|
12
13
|
s.homepage = "https://github.com/jruby/jruby-lint"
|
13
14
|
s.summary = %q{See how ready your Ruby code is to run on JRuby.}
|
14
15
|
s.description = %q{This utility presents hints and suggestions to
|
@@ -26,8 +27,6 @@ Gem::Specification.new do |s|
|
|
26
27
|
s.require_paths = ["lib"]
|
27
28
|
|
28
29
|
s.add_dependency "term-ansicolor"
|
29
|
-
s.add_dependency "jruby-openssl"
|
30
|
-
s.add_dependency "nokogiri", ">= 1.5.0.beta.4"
|
31
30
|
s.add_development_dependency "rake"
|
32
31
|
s.add_development_dependency "rspec", ">= 2.5"
|
33
32
|
s.add_development_dependency "rspec-given"
|
data/lib/jruby/lint/ast.rb
CHANGED
data/lib/jruby/lint/checkers.rb
CHANGED
@@ -10,6 +10,24 @@ module JRuby::Lint
|
|
10
10
|
@checkers ||= []
|
11
11
|
end
|
12
12
|
|
13
|
+
# Note: for parentage methods below -1 is current visited, -2 is parent ,...
|
14
|
+
|
15
|
+
##
|
16
|
+
# What is parent during visit of the current node being visited.
|
17
|
+
def parent
|
18
|
+
collector.stack.size >= 2 ? collector.stack[-2] : nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def grand_parent
|
22
|
+
collector.stack.size >= 3 ? collector.stack[-3] : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# source line with line node provides
|
27
|
+
def src_line(line)
|
28
|
+
collector.contents.split(/\n/)[line]
|
29
|
+
end
|
30
|
+
|
13
31
|
attr_accessor :collector
|
14
32
|
end
|
15
33
|
|
@@ -22,6 +40,5 @@ require 'jruby/lint/checkers/gem'
|
|
22
40
|
require 'jruby/lint/checkers/gemspec'
|
23
41
|
require 'jruby/lint/checkers/thread_critical'
|
24
42
|
require 'jruby/lint/checkers/object_space'
|
25
|
-
require 'jruby/lint/checkers/timeout'
|
26
43
|
require 'jruby/lint/checkers/system'
|
27
44
|
require 'jruby/lint/checkers/nonatomic'
|
@@ -1,47 +1,30 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class ForkExec
|
4
|
-
include Checker
|
1
|
+
class JRuby::Lint::Checkers::ForkExec
|
2
|
+
include JRuby::Lint::Checker
|
5
3
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
proc { @call_node = nil }
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def visitFCallNode(node)
|
18
|
-
add_finding(node) if fork_exec?(node)
|
4
|
+
def visitCallNode(node)
|
5
|
+
if fork?(node)
|
6
|
+
@call_node = node
|
7
|
+
child = node.child_nodes.first
|
8
|
+
if child && %w(COLON3NODE CONSTNODE).include?(child.node_type.to_s) && child.name == :Kernel
|
9
|
+
add_finding(node)
|
19
10
|
end
|
11
|
+
proc { @call_node = nil }
|
12
|
+
end
|
13
|
+
end
|
20
14
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
15
|
+
def visitFCallNode(node)
|
16
|
+
add_finding node if fork?(node)
|
17
|
+
end
|
26
18
|
|
27
|
-
|
28
|
-
|
29
|
-
|
19
|
+
def visitVCallNode(node)
|
20
|
+
add_finding node if fork?(node) && !@call_node
|
21
|
+
end
|
30
22
|
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
23
|
+
def fork?(node)
|
24
|
+
node.name == :fork
|
25
|
+
end
|
42
26
|
|
43
|
-
|
44
|
-
|
45
|
-
end
|
27
|
+
def add_finding(node)
|
28
|
+
collector.add_finding('Kernel#fork is not implemented on JRuby.', [:fork, :error], node.line+1)
|
46
29
|
end
|
47
30
|
end
|
@@ -1,45 +1,59 @@
|
|
1
|
-
module JRuby::Lint
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def f.to_s
|
9
|
-
message
|
10
|
-
end
|
1
|
+
module JRuby::Lint::Checkers
|
2
|
+
module CheckGemNode
|
3
|
+
def self.add_wiki_link_finding(collector)
|
4
|
+
unless @added_wiki_link
|
5
|
+
collector.add_finding("For more on gem compatibility see http://wiki.jruby.org/C-Extension-Alternatives", [:gems, :info]).tap do |f|
|
6
|
+
def f.to_s
|
7
|
+
message
|
11
8
|
end
|
12
|
-
@added_wiki_link = true
|
13
9
|
end
|
10
|
+
@added_wiki_link = true
|
14
11
|
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def gem_name(node)
|
15
|
+
first_arg = node&.args_node&.child_nodes[0]
|
16
|
+
first_arg.value.to_s if first_arg&.node_type&.to_s == "STRNODE"
|
17
|
+
end
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
if
|
19
|
-
|
19
|
+
def jruby_gem_entry?(node)
|
20
|
+
node&.args_node&.child_nodes.each do |child|
|
21
|
+
if child&.node_type&.to_s == "HASHNODE"
|
22
|
+
child.pairs.each do |pair|
|
23
|
+
return false if pair.key.name == :platform &&
|
24
|
+
pair.value.name != :jruby
|
25
|
+
end
|
20
26
|
end
|
21
|
-
rescue
|
22
|
-
nil
|
23
27
|
end
|
24
28
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
collector.findings << Finding.new(msg, [:gems, :warning], call_node.position)
|
29
|
+
# platform(:mri, ...) { gem 'rdiscount' }
|
30
|
+
# FIXME: Esoteric use of platform(...) { group(...) {} } is still broken
|
31
|
+
if grand_parent.kind_of?(org::jruby::ast::CallNode) &&
|
32
|
+
grand_parent.name == :platforms
|
33
|
+
grand_parent.args_node.child_nodes.each do |child|
|
34
|
+
return false if child&.name != :jruby
|
32
35
|
end
|
33
36
|
end
|
34
|
-
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
include CheckGemNode
|
38
|
+
true
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
def check_gem(collector, call_node)
|
42
|
+
@gems ||= collector.project.libraries.gems
|
43
|
+
gem_name = gem_name(call_node)
|
44
|
+
if gem_name && jruby_gem_entry?(call_node) && instructions = @gems[gem_name]
|
45
|
+
CheckGemNode.add_wiki_link_finding(collector)
|
46
|
+
msg = "Found gem '#{gem_name}' which is reported to have some issues:\n#{instructions}"
|
47
|
+
collector.add_finding(msg, [:gems, :warning], call_node.line+1)
|
42
48
|
end
|
43
49
|
end
|
44
50
|
end
|
51
|
+
|
52
|
+
class Gem
|
53
|
+
include JRuby::Lint::Checker, CheckGemNode
|
54
|
+
|
55
|
+
def visitFCallNode(node)
|
56
|
+
check_gem(collector, node) if node.name == :gem
|
57
|
+
end
|
58
|
+
end
|
45
59
|
end
|
@@ -18,12 +18,12 @@ module JRuby::Lint
|
|
18
18
|
# DAsgnNode |s| &0 >0
|
19
19
|
# NilImplicitNode |nil|
|
20
20
|
# BlockNode
|
21
|
-
if node.name ==
|
21
|
+
if node.name == :new && # new
|
22
22
|
node.args_node.nil? && # no args
|
23
23
|
node.iter_node && # with a block
|
24
24
|
node.receiver_node.node_type.to_s == "COLON2NODE" && # :: - Colon2
|
25
|
-
node.receiver_node.name ==
|
26
|
-
node.receiver_node.left_node.name ==
|
25
|
+
node.receiver_node.name == :Specification && # ::Specification
|
26
|
+
node.receiver_node.left_node.name == :Gem # Gem::Specification
|
27
27
|
arg_node = find_first(node.iter_node.var_node) {|n| n.respond_to?(:name) }
|
28
28
|
@gemspec_block_var = arg_node.name
|
29
29
|
return proc { @gemspec_block_var = nil }
|
@@ -36,7 +36,7 @@ module JRuby::Lint
|
|
36
36
|
# ArrayNode
|
37
37
|
# StrNode =="rdiscount"
|
38
38
|
if @gemspec_block_var &&
|
39
|
-
node.name ==
|
39
|
+
node.name == :add_dependency &&
|
40
40
|
node.receiver_node.name == @gemspec_block_var
|
41
41
|
check_gem(collector, node)
|
42
42
|
end
|
@@ -2,38 +2,68 @@ module JRuby::Lint
|
|
2
2
|
module Checkers
|
3
3
|
class NonAtomic
|
4
4
|
include Checker
|
5
|
+
IVAR = org::jruby::ast::InstVarNode
|
6
|
+
CVAR = org::jruby::ast::ClassVarNode
|
7
|
+
|
8
|
+
OPERATORS = [:+, :-, :/, :*, :&, :|, :^, :>>, :<<, :%, :**]
|
5
9
|
|
6
10
|
def visitOpAsgnOrNode(node)
|
11
|
+
@last = node
|
7
12
|
check_nonatomic(node, node.first_node)
|
8
13
|
end
|
9
14
|
|
10
15
|
def visitOpAsgnAndNode(node)
|
16
|
+
@last = node
|
11
17
|
check_nonatomic(node, node.first_node)
|
12
18
|
end
|
13
19
|
|
14
20
|
def visitOpElementAsgnNode(node)
|
15
|
-
|
21
|
+
name = src_line(node.line).split(node.operator_name + '=', 2)[0].strip
|
22
|
+
add_finding(collector, node, name)
|
16
23
|
end
|
17
24
|
|
18
25
|
def visitOpAsgnNode(node)
|
19
|
-
check_nonatomic(node, node.receiver_node)
|
26
|
+
check_nonatomic(node, node.receiver_node, node.variable_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def operator_op_assignment?(node, type)
|
30
|
+
rhs = node.value_node
|
31
|
+
rhs.kind_of?(org::jruby::ast::CallNode) &&
|
32
|
+
OPERATORS.include?(rhs.name) &&
|
33
|
+
rhs.receiver_node.kind_of?(type)
|
20
34
|
end
|
21
35
|
|
22
|
-
def
|
36
|
+
def visitInstAsgnNode(node)
|
37
|
+
if !@last && operator_op_assignment?(node, IVAR) ||
|
38
|
+
@last && parent != @last
|
39
|
+
check_nonatomic(node, node)
|
40
|
+
end
|
41
|
+
@last = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def visitClassVarAsgnNode(node)
|
45
|
+
if !@last && operator_op_assignment?(node, CVAR) ||
|
46
|
+
@last && parent != @last
|
47
|
+
check_nonatomic(node, node)
|
48
|
+
end
|
49
|
+
@last = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_nonatomic(orig_node, risk_node, name=nil)
|
23
53
|
case risk_node
|
24
54
|
when org::jruby::ast::LocalVarNode,
|
25
55
|
org::jruby::ast::DVarNode
|
26
56
|
# ok...mostly-safe cases
|
57
|
+
false
|
27
58
|
else
|
28
|
-
add_finding(collector, orig_node)
|
59
|
+
add_finding(collector, orig_node, name || risk_node.name)
|
60
|
+
true
|
29
61
|
end
|
30
62
|
end
|
31
63
|
|
32
|
-
def add_finding(collector, node)
|
33
|
-
collector.
|
34
|
-
[:nonatomic, :warning], node.position)
|
64
|
+
def add_finding(collector, node, name)
|
65
|
+
collector.add_finding("Non-local operator assignment (#{name}) is not guaranteed to be atomic.", [:nonatomic, :warning], node.line+1)
|
35
66
|
end
|
36
67
|
end
|
37
68
|
end
|
38
69
|
end
|
39
|
-
|
@@ -2,14 +2,16 @@ module JRuby::Lint
|
|
2
2
|
module Checkers
|
3
3
|
class ObjectSpace
|
4
4
|
include Checker
|
5
|
-
METHODS =
|
5
|
+
METHODS = [:each_object, :_id2ref]
|
6
|
+
OK_ARGS = [:Class, :Module]
|
6
7
|
|
7
8
|
def visitCallNode(node)
|
8
9
|
if METHODS.include?(node.name)
|
9
10
|
begin
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
return unless node.receiver_node.node_type.to_s == "CONSTNODE" &&
|
12
|
+
node.receiver_node.name == :ObjectSpace
|
13
|
+
return if node.args_node && node.args_node.size == 1 &&
|
14
|
+
OK_ARGS.include?(node.args_node.first.name)
|
13
15
|
add_finding(collector, node)
|
14
16
|
rescue
|
15
17
|
end
|
@@ -17,8 +19,8 @@ module JRuby::Lint
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def add_finding(collector, node)
|
20
|
-
collector.
|
21
|
-
[:objectspace, :warning], node.
|
22
|
+
collector.add_finding("Use of ObjectSpace is expensive and disabled by default. Use -X+O to enable.",
|
23
|
+
[:objectspace, :warning], node.line+1)
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
@@ -5,7 +5,7 @@ module JRuby::Lint
|
|
5
5
|
|
6
6
|
# Make sure to follow all Kernel.system calls
|
7
7
|
def visitCallNode(node)
|
8
|
-
if node.name ==
|
8
|
+
if node.name == :system || node.name == :'`'
|
9
9
|
@call_node = node
|
10
10
|
add_finding(node) if red_flag?(node)
|
11
11
|
proc { @call_node = nil }
|
@@ -14,14 +14,14 @@ module JRuby::Lint
|
|
14
14
|
|
15
15
|
# Visits the function calls for system
|
16
16
|
def visitFCallNode(node)
|
17
|
-
if node.name ==
|
17
|
+
if node.name == :system
|
18
18
|
add_finding(node) if red_flag?(node)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def add_finding(node)
|
23
|
-
collector.
|
24
|
-
[:system, :warning], node.
|
23
|
+
collector.add_finding("Calling Kernel.system('ruby ...') will get called in-process. Sometimes this works differently than expected",
|
24
|
+
[:system, :warning], node.line+1)
|
25
25
|
end
|
26
26
|
|
27
27
|
# Defines red_flag when argument matches ruby
|
@@ -3,12 +3,12 @@ module JRuby::Lint
|
|
3
3
|
class ThreadCritical
|
4
4
|
include Checker
|
5
5
|
|
6
|
-
METHODS =
|
6
|
+
METHODS = [:critical, :critical=]
|
7
7
|
|
8
8
|
def visitCallNode(node)
|
9
9
|
if METHODS.include?(node.name)
|
10
10
|
begin
|
11
|
-
if node.receiver_node.node_type.to_s == "CONSTNODE" && node.receiver_node.name ==
|
11
|
+
if node.receiver_node.node_type.to_s == "CONSTNODE" && node.receiver_node.name == :Thread
|
12
12
|
add_finding(collector, node)
|
13
13
|
end
|
14
14
|
rescue
|
@@ -18,8 +18,8 @@ module JRuby::Lint
|
|
18
18
|
alias visitAttrAssignNode visitCallNode
|
19
19
|
|
20
20
|
def add_finding(collector, node)
|
21
|
-
collector.
|
22
|
-
[:threads, :warning], node.
|
21
|
+
collector.add_finding("Use of Thread.critical is discouraged. Use a Mutex instead.",
|
22
|
+
[:threads, :warning], node.line+1)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|