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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -1
  3. data/README.md +2 -8
  4. data/jruby-lint.gemspec +2 -3
  5. data/lib/jruby/lint/ast.rb +1 -1
  6. data/lib/jruby/lint/checkers.rb +18 -1
  7. data/lib/jruby/lint/checkers/fork_exec.rb +22 -39
  8. data/lib/jruby/lint/checkers/gem.rb +44 -30
  9. data/lib/jruby/lint/checkers/gemspec.rb +4 -4
  10. data/lib/jruby/lint/checkers/nonatomic.rb +38 -8
  11. data/lib/jruby/lint/checkers/object_space.rb +8 -6
  12. data/lib/jruby/lint/checkers/system.rb +4 -4
  13. data/lib/jruby/lint/checkers/thread_critical.rb +4 -4
  14. data/lib/jruby/lint/cli.rb +8 -1
  15. data/lib/jruby/lint/collectors.rb +18 -7
  16. data/lib/jruby/lint/finding.rb +1 -1
  17. data/lib/jruby/lint/libraries.rb +21 -30
  18. data/lib/jruby/lint/project.rb +3 -3
  19. data/lib/jruby/lint/reporters/html.rb +4 -3
  20. data/lib/jruby/lint/reporters/jruby-lint.html.erb +10 -1
  21. data/lib/jruby/lint/reporters/text.rb +5 -4
  22. data/lib/jruby/lint/version.rb +1 -1
  23. data/spec/fixtures/C-Extension-Alternatives.md +81 -0
  24. data/spec/jruby/lint/ast_spec.rb +6 -6
  25. data/spec/jruby/lint/checkers_spec.rb +91 -62
  26. data/spec/jruby/lint/collectors_spec.rb +3 -9
  27. data/spec/jruby/lint/finding_spec.rb +10 -10
  28. data/spec/jruby/lint/libraries_spec.rb +13 -13
  29. data/spec/jruby/lint/project_spec.rb +34 -40
  30. data/spec/jruby/lint/reporters_spec.rb +22 -13
  31. data/spec/jruby/lint/version_spec.rb +1 -1
  32. data/spec/spec_helper.rb +1 -1
  33. metadata +30 -96
  34. data/lib/jruby/lint/checkers/timeout.rb +0 -21
  35. data/lib/jruby/lint/github.crt +0 -23
  36. data/spec/fixtures/C-Extension-Alternatives.html +0 -339
  37. data/spec/jruby/lint/cli_spec.rb +0 -73
@@ -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
@@ -8,4 +8,5 @@ gem 'guard'
8
8
  gem 'guard-rspec'
9
9
  gem 'growl'
10
10
  gem 'rb-fsevent'
11
- gem 'rcov'
11
+
12
+
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) and Kernel#exec
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
@@ -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 1.0", "GPL 2", "LGPL 2.1"]
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"
@@ -32,7 +32,7 @@ module JRuby::Lint
32
32
  class Visitor
33
33
  include Enumerable
34
34
  include org.jruby.ast.visitor.NodeVisitor
35
- attr_reader :ast
35
+ attr_reader :ast, :stack
36
36
 
37
37
  def initialize(ast)
38
38
  @ast = ast
@@ -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
- module JRuby::Lint
2
- module Checkers
3
- class ForkExec
4
- include Checker
1
+ class JRuby::Lint::Checkers::ForkExec
2
+ include JRuby::Lint::Checker
5
3
 
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)
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
- def visitVCallNode(node)
22
- if fork_exec?(node)
23
- add_finding(node) unless @call_node
24
- end
25
- end
15
+ def visitFCallNode(node)
16
+ add_finding node if fork?(node)
17
+ end
26
18
 
27
- def fork_exec?(node)
28
- node.name == "fork" || node.name == "exec"
29
- end
19
+ def visitVCallNode(node)
20
+ add_finding node if fork?(node) && !@call_node
21
+ end
30
22
 
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
23
+ def fork?(node)
24
+ node.name == :fork
25
+ end
42
26
 
43
- collector.findings << Finding.new(msg, tags, node.position)
44
- end
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 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
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
- 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
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
- 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)
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
- class Gem
37
- include Checker
38
- include CheckGemNode
38
+ true
39
+ end
39
40
 
40
- def visitFCallNode(node)
41
- check_gem(collector, node) if node.name == "gem"
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 == "new" && # new
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 == "Specification" && # ::Specification
26
- node.receiver_node.left_node.name == "Gem" # Gem::Specification
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 == "add_dependency" &&
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
- add_finding(collector, node)
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 check_nonatomic(orig_node, risk_node)
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.findings << Finding.new("Non-local operator assignment is not guaranteed to be atomic",
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 = %w(each_object _id2ref)
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
- 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)
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.findings << Finding.new("Use of ObjectSpace is expensive and disabled by default. Use -X+O to enable.",
21
- [:objectspace, :warning], node.position)
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 == "system" || 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 == "system"
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.findings << Finding.new("Calling Kernel.system('ruby ...') will get called in-process. Sometimes this works differently than expected",
24
- [:system, :warning], node.position)
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 = %w(critical critical=)
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 == "Thread"
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.findings << Finding.new("Use of Thread.critical is discouraged. Use a Mutex instead.",
22
- [:threads, :warning], node.position)
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