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.
Files changed (40) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +12 -0
  3. data/Guardfile +12 -0
  4. data/History.txt +4 -0
  5. data/README.md +62 -0
  6. data/Rakefile +24 -0
  7. data/bin/jrlint +5 -0
  8. data/jruby-lint.gemspec +38 -0
  9. data/lib/jruby/lint.rb +19 -0
  10. data/lib/jruby/lint/ast.rb +56 -0
  11. data/lib/jruby/lint/checkers.rb +24 -0
  12. data/lib/jruby/lint/checkers/fork_exec.rb +47 -0
  13. data/lib/jruby/lint/checkers/gem.rb +45 -0
  14. data/lib/jruby/lint/checkers/gemspec.rb +41 -0
  15. data/lib/jruby/lint/checkers/object_space.rb +25 -0
  16. data/lib/jruby/lint/checkers/thread_critical.rb +26 -0
  17. data/lib/jruby/lint/cli.rb +61 -0
  18. data/lib/jruby/lint/collectors.rb +75 -0
  19. data/lib/jruby/lint/collectors/bundler.rb +9 -0
  20. data/lib/jruby/lint/collectors/gemspec.rb +9 -0
  21. data/lib/jruby/lint/collectors/rake.rb +9 -0
  22. data/lib/jruby/lint/collectors/ruby.rb +14 -0
  23. data/lib/jruby/lint/finding.rb +15 -0
  24. data/lib/jruby/lint/github.crt +76 -0
  25. data/lib/jruby/lint/libraries.rb +107 -0
  26. data/lib/jruby/lint/project.rb +56 -0
  27. data/lib/jruby/lint/reporters.rb +35 -0
  28. data/lib/jruby/lint/version.rb +5 -0
  29. data/spec/fixtures/C-Extension-Alternatives.html +557 -0
  30. data/spec/jruby/lint/ast_spec.rb +23 -0
  31. data/spec/jruby/lint/checkers_spec.rb +135 -0
  32. data/spec/jruby/lint/cli_spec.rb +66 -0
  33. data/spec/jruby/lint/collectors_spec.rb +34 -0
  34. data/spec/jruby/lint/finding_spec.rb +31 -0
  35. data/spec/jruby/lint/libraries_spec.rb +56 -0
  36. data/spec/jruby/lint/project_spec.rb +77 -0
  37. data/spec/jruby/lint/reporters_spec.rb +42 -0
  38. data/spec/jruby/lint/version_spec.rb +6 -0
  39. data/spec/spec_helper.rb +30 -0
  40. metadata +194 -0
@@ -0,0 +1,23 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe JRuby::Lint::AST::Visitor do
4
+ Given(:ast) { JRuby.parse(script) }
5
+ Given(:visitor) { JRuby::Lint::AST::Visitor.new(ast) }
6
+
7
+ # RootNode
8
+ # NewlineNode
9
+ # FCallOneArgNode |puts|
10
+ # ArrayNode
11
+ # StrNode =="hello"
12
+ Given(:script) { %{puts "hello"} }
13
+
14
+ context "visits all nodes" do
15
+ When { visitor.each_node { @count ||= 0; @count += 1} }
16
+ Then { @count.should == 5 }
17
+ end
18
+
19
+ context "selects nodes" do
20
+ When { @nodes = visitor.select {|n| n.node_type == org.jruby.ast.NodeType::STRNODE } }
21
+ Then { @nodes.size.should == 1 && @nodes.first.value.to_s.should == "hello" }
22
+ end
23
+ end
@@ -0,0 +1,135 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe JRuby::Lint::Checker do
4
+ context "checkers" do
5
+ subject { Class.new { include JRuby::Lint::Checker } }
6
+
7
+ it "finds all loaded checkers" do
8
+ JRuby::Lint::Checker.loaded_checkers.should include(subject)
9
+ end
10
+ end
11
+ end
12
+
13
+ describe JRuby::Lint::Checkers do
14
+ Given(:gems) { { "rdiscount" => "may not work", "bson_ext" => "not needed" } }
15
+ Given(:project) { double("project").tap {|p| p.stub_chain("libraries.gems") { gems } } }
16
+ Given(:collector) do
17
+ JRuby::Lint::Collector.new(project).tap do |c|
18
+ c.contents = script
19
+ c.checkers = [checker]
20
+ checker.collector = c
21
+ end
22
+ end
23
+
24
+ context "Fork/exec checker" do
25
+ Given(:checker) { JRuby::Lint::Checkers::ForkExec.new }
26
+
27
+ context "detects fcall-style" do
28
+ # FCallNoArgBlockNode |fork|
29
+ Given(:script) { "fork { }; exec('cmd')" }
30
+ When { collector.run }
31
+ Then { collector.findings.size.should == 2 }
32
+ end
33
+
34
+ context "detects vcall-style" do
35
+ # VCallNode |fork|
36
+ Given(:script) { "fork" }
37
+ When { collector.run }
38
+ Then { collector.findings.size.should == 1 }
39
+ end
40
+
41
+ context "does not detect call-style" do
42
+ # CallNoArgNode |fork|
43
+ # VCallNode |fork|
44
+ Given(:script) { "fork.fork" }
45
+ When { collector.run }
46
+ Then { collector.findings.size.should == 0 }
47
+ end
48
+
49
+ context "detects Kernel::fork style" do
50
+ # CallNoArgNode |fork|
51
+ # ConstNode |Kernel|
52
+ Given(:script) { "Kernel::fork; Kernel::exec('cmd')" }
53
+ When { collector.run }
54
+ Then { collector.findings.size.should == 2 }
55
+ end
56
+ end
57
+
58
+ context "Gem checker" do
59
+ Given(:checker) { JRuby::Lint::Checkers::Gem.new }
60
+
61
+ context "creates a finding for a gem mentioned in the libraries" do
62
+ Given(:script) { "gem 'rdiscount'" }
63
+ When { collector.run }
64
+ Then { collector.findings.size.should == 2 }
65
+ end
66
+
67
+ context "creates one finding to mention the wiki for gem compatibility" do
68
+ Given(:script) { "gem 'rdiscount'; gem 'bson_ext'" }
69
+ When { collector.run }
70
+ Then { collector.findings.size.should == 3 }
71
+ end
72
+
73
+ context "does not create a finding for a gem not mentioned in the gems info" do
74
+ Given(:script) { "gem 'json_pure'" }
75
+ When { collector.run }
76
+ Then { collector.findings.size.should == 0 }
77
+ end
78
+
79
+ context "only checks calls to #gem" do
80
+ Given(:script) { "require 'rdiscount'" }
81
+ When { collector.run }
82
+ Then { collector.findings.size.should == 0 }
83
+ end
84
+ end
85
+
86
+ context "Gemspec checker" do
87
+ Given(:checker) { JRuby::Lint::Checkers::Gemspec.new }
88
+
89
+ Given(:script) { "Gem::Specification.new do |s|" +
90
+ "\ns.name = 'hello'\ns.add_dependency 'rdiscount'\n" +
91
+ "s.add_development_dependency 'ruby-debug19'\nend\n" }
92
+
93
+ When { collector.run }
94
+ Then { collector.findings.size.should == 2 }
95
+ Then { collector.findings.detect{|f| f.message =~ /rdiscount/ }.should be_true }
96
+ end
97
+
98
+ context "Thread.critical checker" do
99
+ Given(:checker) { JRuby::Lint::Checkers::ThreadCritical.new }
100
+
101
+ context "read" do
102
+ Given(:script) { "begin \n Thread.critical \n end"}
103
+ When { collector.run }
104
+ Then { collector.findings.size.should == 1 }
105
+ end
106
+
107
+ context "assign" do
108
+ Given(:script) { "begin \n Thread.critical = true \n ensure Thread.critical = false \n end"}
109
+ When { collector.run }
110
+ Then { collector.findings.size.should == 2 }
111
+ end
112
+ end
113
+
114
+ context "ObjectSpace" do
115
+ Given(:checker) { JRuby::Lint::Checkers::ObjectSpace.new }
116
+
117
+ context "_id2ref usage" do
118
+ Given(:script) { "ObjectSpace._id2ref(obj)"}
119
+ When { collector.run }
120
+ Then { collector.findings.size.should == 1 }
121
+ end
122
+
123
+ context "each_object usage" do
124
+ Given(:script) { "ObjectSpace.each_object { }"}
125
+ When { collector.run }
126
+ Then { collector.findings.size.should == 1 }
127
+ end
128
+
129
+ context "each_object(Class) usage is ok" do
130
+ Given(:script) { "ObjectSpace.each_object(Class) { }"}
131
+ When { collector.run }
132
+ Then { collector.findings.size.should == 0 }
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,66 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+ require 'jruby/lint/cli'
3
+
4
+ describe JRuby::Lint::CLI do
5
+ context "when launched" do
6
+ Given(:command) { "ruby -I#{project_dir}/lib -S #{project_dir}/bin/jrlint #{args}" }
7
+
8
+ context "with the help option" do
9
+ Given(:args) { "--help" }
10
+ When { run_simple(command) }
11
+ Then do
12
+ output_from(command).should =~ /help.*This message/
13
+ @last_exit_status.should == 0
14
+ end
15
+ end
16
+
17
+ context "with the version option" do
18
+ Given(:args) { "--version" }
19
+ When { run_simple(command) }
20
+ Then do
21
+ output_from(command).should =~ /version #{JRuby::Lint::VERSION}/
22
+ @last_exit_status.should == 0
23
+ end
24
+ end
25
+
26
+ context "with a dash-e option" do
27
+ Given(:args) { "-e true"}
28
+ When { run_simple(command) }
29
+ Then do
30
+ output_from(command).should =~ /Processed 1 expression/
31
+ @last_exit_status.should == 0
32
+ end
33
+ end
34
+
35
+ context "with a file argument" do
36
+ Given(:args) { "sample.rb" }
37
+ Given { write_file("sample.rb", "puts 'hello'"); write_file("example.rb", "puts 'hello'") }
38
+ When { run_simple(command) }
39
+ Then do
40
+ output_from(command).should =~ /Processed 1 file/
41
+ @last_exit_status.should == 0
42
+ end
43
+ end
44
+
45
+ context "with no arguments" do
46
+ Given(:args) { "" }
47
+
48
+ context "and some files to process" do
49
+ Given { write_file("Rakefile", "") }
50
+ When { run_simple(command) }
51
+ Then do
52
+ output = output_from(command)
53
+ output.should =~ /JRuby-Lint version #{JRuby::Lint::VERSION}/
54
+ output.should =~ /Processed 1 file/
55
+ output.should =~ /OK/
56
+ @last_exit_status.should == 0
57
+ end
58
+ end
59
+
60
+ context "and no files to process" do
61
+ When { run_simple(command) }
62
+ Then { output_from(command).should =~ /Processed 0 files/ }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,34 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe JRuby::Lint::Collector do
4
+ Given(:collector) { JRuby::Lint::Collector.new }
5
+ Given(:checker_class) { Class.new { include JRuby::Lint::Checker } }
6
+
7
+ context "loads detected checkers" do
8
+ When { checker_class }
9
+ Then { collector.checkers.detect {|c| checker_class === c }.should be_true }
10
+ end
11
+
12
+ context "invokes all checkers" do
13
+ Given(:checker) do
14
+ double("checker").tap do |checker|
15
+ checker.should_receive(:visitTrueNode)
16
+ collector.checkers = [checker]
17
+ end
18
+ end
19
+ When { collector.contents = 'true' }
20
+ Then { collector.run }
21
+ end
22
+
23
+ context "loads an AST" do
24
+ When { collector.contents = 'puts "hello"' }
25
+ When { @ast = collector.ast }
26
+ Then { @ast.inspect.should =~ /"hello"/m }
27
+ end
28
+
29
+ context "reports syntax errors as findings" do
30
+ When { collector.contents = '<% true %>' }
31
+ When { collector.run }
32
+ Then { collector.findings.size.should == 1 }
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe JRuby::Lint::Finding do
4
+ Given(:message) { "bad code" }
5
+ Given(:file) { "file" }
6
+ Given(:line) { 19 }
7
+ Given(:tags) { ["threads", "info"] }
8
+
9
+ context "has a message, location and tags" do
10
+ When { @finding = JRuby::Lint::Finding.new(message, tags, file, line) }
11
+ Then { @finding.message.should == message }
12
+ Then { @finding.tags.should == tags }
13
+ Then { @finding.file.should == file }
14
+ Then { @finding.line.should == line }
15
+ Then { @finding.to_s.should == "#{file}:#{line}: [#{tags.join(', ')}] #{message}" }
16
+ end
17
+
18
+ context "can receive location from SourcePosition" do
19
+ Given(:source_position) { org.jruby.lexer.yacc.SimpleSourcePosition.new(file, line) }
20
+
21
+ When { @finding = JRuby::Lint::Finding.new(message, tags, source_position) }
22
+ Then { @finding.file.should == file }
23
+ Then { @finding.line.should == line + 1 }
24
+ end
25
+
26
+ context "converts all tags to strings" do
27
+ Given(:tags) { [1, :two, 3.0] }
28
+ When { @finding = JRuby::Lint::Finding.new(message, tags, file, line) }
29
+ Then { @finding.tags.should == ["1", "two", "3.0"] }
30
+ end
31
+ end
@@ -0,0 +1,56 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe JRuby::Lint::Libraries do
4
+ Given(:cache_dir) { ENV['JRUBY_LINT_CACHE'] }
5
+ Given(:cache) { JRuby::Lint::Libraries::Cache.new(cache_dir) }
6
+
7
+ context "cache" do
8
+ Given(:cache_dir) { current_dir }
9
+
10
+ context "with net access", :requires_net => true do
11
+ context "fetch" do
12
+ When { cache.fetch('C-Extension-Alternatives') }
13
+ Then { check_file_presence('C-Extension-Alternatives.html', true) }
14
+ end
15
+
16
+ context "refreshes a file that's too old" do
17
+ Given { write_file('C-Extension-Alternatives.html', 'alternatives') }
18
+ Given(:yesterday) { Time.now - 25 * 60 * 60 }
19
+ Given { File.utime yesterday, yesterday, File.join(current_dir, 'C-Extension-Alternatives.html')}
20
+ When { cache.fetch('C-Extension-Alternatives') }
21
+ Then { File.mtime(File.join(current_dir, 'C-Extension-Alternatives.html')).should > yesterday }
22
+ end
23
+ end
24
+
25
+ context "with no net access" do
26
+ Given { Net::HTTP.should_not_receive(:start) }
27
+
28
+ context "store assumes .html extension by default" do
29
+ When { cache.store('hello.yml', 'hi')}
30
+ Then { check_file_presence('hello.yml', true) }
31
+ end
32
+
33
+ context "store assumes .html extension by default" do
34
+ When { cache.store('hello', 'hi')}
35
+ Then { check_file_presence('hello.html', true) }
36
+ end
37
+
38
+ context "fetch should not access net when file is cached" do
39
+ Given { write_file('hello.html', 'hi') }
40
+ When { cache.fetch('hello') }
41
+ end
42
+ end
43
+ end
44
+
45
+ context "c extensions list" do
46
+ Given(:list) { JRuby::Lint::Libraries::CExtensions.new(cache) }
47
+ When { list.load }
48
+ Then { list.gems.keys.should include("rdiscount", "rmagick")}
49
+ end
50
+
51
+ context "aggregate information" do
52
+ Given(:info) { JRuby::Lint::Libraries.new(cache) }
53
+ When { info.load }
54
+ Then { info.gems.keys.should include("rdiscount", "rmagick")}
55
+ end
56
+ end
@@ -0,0 +1,77 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe JRuby::Lint::Project do
4
+ Given(:project) { in_current_dir { JRuby::Lint::Project.new.tap {|p| p.reporters.clear } } }
5
+
6
+ context "collects Ruby scripts" do
7
+ Given { write_file('script.rb', '') }
8
+ When { @collectors = project.collectors }
9
+ Then { @collectors.size.should == 1 }
10
+ Then { @collectors.first.should be_instance_of(JRuby::Lint::Collectors::Ruby) }
11
+ end
12
+
13
+ context "collects Bundler Gemfiles" do
14
+ Given { write_file('Gemfile', '') }
15
+ When { @collectors = project.collectors }
16
+ Then { @collectors.size.should == 1 }
17
+ Then { @collectors.first.should be_instance_of(JRuby::Lint::Collectors::Bundler) }
18
+ end
19
+
20
+ context "collects Rakefiles" do
21
+ Given { write_file('Rakefile', '') }
22
+ When { @collectors = project.collectors }
23
+ Then { @collectors.size.should == 1 }
24
+ Then { @collectors.first.should be_instance_of(JRuby::Lint::Collectors::Rake) }
25
+ end
26
+
27
+ context "collects gemspecs" do
28
+ Given { write_file('temp.gemspec', '') }
29
+ When { @collectors = project.collectors }
30
+ Then { @collectors.size.should == 1 }
31
+ Then { @collectors.first.should be_instance_of(JRuby::Lint::Collectors::Gemspec) }
32
+ end
33
+
34
+ context "aggregates findings from all collectors" do
35
+ Given(:collector1) do
36
+ double("collector 1").tap do |c1|
37
+ c1.should_receive(:run)
38
+ c1.stub!(:findings).and_return [double("finding 1")]
39
+ end
40
+ end
41
+ Given(:collector2) do
42
+ double("collector 2").tap do |c2|
43
+ c2.should_receive(:run)
44
+ c2.stub!(:findings).and_return [double("finding 2")]
45
+ end
46
+ end
47
+
48
+ When { project.collectors.replace([collector1, collector2]) }
49
+ When { findings = project.run }
50
+
51
+ Then { project.findings.size.should == 2 }
52
+ end
53
+
54
+ context "reports findings" do
55
+ Given(:finding) { double "finding" }
56
+
57
+ Given(:collector) do
58
+ double("collector").tap do |c|
59
+ c.should_receive(:run)
60
+ c.stub!(:findings).and_return [finding]
61
+ end
62
+ end
63
+
64
+ Given(:reporter) do
65
+ double("reporter").tap do |r|
66
+ r.should_receive(:report).with([finding])
67
+ end
68
+ end
69
+
70
+ When do
71
+ project.collectors.replace [collector]
72
+ project.reporters.replace [reporter]
73
+ end
74
+
75
+ Then { project.run }
76
+ end
77
+ end
@@ -0,0 +1,42 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe JRuby::Lint::Reporters do
4
+ Given(:project) { double "project", :tags => %w(warning error info) }
5
+
6
+ context "Text reporter" do
7
+ Given(:reporter) { JRuby::Lint::Reporters::Text.new(project, output) }
8
+
9
+ context "with a finding sharing a tag with the project" do
10
+ Given(:finding) { double "finding", :to_s => "hello", :tags => %w(info) }
11
+ Given(:output) { double("output").tap {|o| o.should_receive(:puts).with("hello") } }
12
+
13
+ Then { reporter.report [finding] }
14
+ end
15
+
16
+ context "with a finding sharing no tags with the project" do
17
+ Given(:finding) { double "finding", :to_s => "hello", :tags => %w(debug) }
18
+ Given(:output) { double("output").tap {|o| o.should_not_receive(:puts) } }
19
+
20
+ Then { reporter.report [finding] }
21
+ end
22
+ end
23
+
24
+ context "Color text reporter" do
25
+ include Term::ANSIColor
26
+ Given(:reporter) { JRuby::Lint::Reporters::ANSIColor.new(project, output) }
27
+
28
+ context "shows a finding tagged 'error' in red" do
29
+ Given(:finding) { double "finding", :to_s => "hello", :tags => %w(error) }
30
+ Given(:output) { double("output").tap {|o| o.should_receive(:puts).with(red("hello")) } }
31
+
32
+ Then { reporter.report [finding] }
33
+ end
34
+
35
+ context "shows a finding tagged 'warning' in yellow" do
36
+ Given(:finding) { double "finding", :to_s => "hello", :tags => %w(warning) }
37
+ Given(:output) { double("output").tap {|o| o.should_receive(:puts).with(yellow("hello")) } }
38
+
39
+ Then { reporter.report [finding] }
40
+ end
41
+ end
42
+ end