jruby-lint 0.4.1 → 0.9.0
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.
- 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
|