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