wool 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/.document +5 -0
  2. data/.gitignore +23 -0
  3. data/LICENSE +45 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +77 -0
  6. data/TODO.md +17 -0
  7. data/VERSION +1 -0
  8. data/bin/wool +4 -0
  9. data/features/step_definitions/wool_steps.rb +39 -0
  10. data/features/support/env.rb +14 -0
  11. data/features/support/testdata/1_input +1 -0
  12. data/features/support/testdata/1_output +1 -0
  13. data/features/support/testdata/2_input +4 -0
  14. data/features/support/testdata/2_output +4 -0
  15. data/features/support/testdata/3_input +8 -0
  16. data/features/support/testdata/3_output +11 -0
  17. data/features/support/testdata/4_input +5 -0
  18. data/features/support/testdata/4_output +5 -0
  19. data/features/wool.feature +24 -0
  20. data/lib/wool.rb +40 -0
  21. data/lib/wool/advice/advice.rb +42 -0
  22. data/lib/wool/advice/comment_advice.rb +37 -0
  23. data/lib/wool/analysis/annotations.rb +34 -0
  24. data/lib/wool/analysis/annotations/next_annotation.rb +26 -0
  25. data/lib/wool/analysis/annotations/parent_annotation.rb +20 -0
  26. data/lib/wool/analysis/annotations/scope_annotation.rb +37 -0
  27. data/lib/wool/analysis/lexical_analysis.rb +165 -0
  28. data/lib/wool/analysis/protocol_registry.rb +32 -0
  29. data/lib/wool/analysis/protocols.rb +82 -0
  30. data/lib/wool/analysis/scope.rb +13 -0
  31. data/lib/wool/analysis/sexp_analysis.rb +98 -0
  32. data/lib/wool/analysis/signature.rb +16 -0
  33. data/lib/wool/analysis/symbol.rb +10 -0
  34. data/lib/wool/analysis/visitor.rb +36 -0
  35. data/lib/wool/analysis/wool_class.rb +47 -0
  36. data/lib/wool/rake/task.rb +42 -0
  37. data/lib/wool/runner.rb +156 -0
  38. data/lib/wool/scanner.rb +160 -0
  39. data/lib/wool/support/module_extensions.rb +84 -0
  40. data/lib/wool/third_party/trollop.rb +845 -0
  41. data/lib/wool/warning.rb +145 -0
  42. data/lib/wool/warnings/comment_spacing.rb +30 -0
  43. data/lib/wool/warnings/extra_blank_lines.rb +29 -0
  44. data/lib/wool/warnings/extra_whitespace.rb +15 -0
  45. data/lib/wool/warnings/line_length.rb +113 -0
  46. data/lib/wool/warnings/misaligned_unindentation.rb +16 -0
  47. data/lib/wool/warnings/operator_spacing.rb +63 -0
  48. data/lib/wool/warnings/rescue_exception.rb +41 -0
  49. data/lib/wool/warnings/semicolon.rb +24 -0
  50. data/lib/wool/warnings/useless_double_quotes.rb +37 -0
  51. data/spec/advice_specs/advice_spec.rb +69 -0
  52. data/spec/advice_specs/comment_advice_spec.rb +38 -0
  53. data/spec/advice_specs/spec_helper.rb +1 -0
  54. data/spec/analysis_specs/annotations_specs/next_prev_annotation_spec.rb +47 -0
  55. data/spec/analysis_specs/annotations_specs/parent_annotation_spec.rb +41 -0
  56. data/spec/analysis_specs/annotations_specs/spec_helper.rb +5 -0
  57. data/spec/analysis_specs/lexical_analysis_spec.rb +179 -0
  58. data/spec/analysis_specs/protocol_registry_spec.rb +58 -0
  59. data/spec/analysis_specs/protocols_spec.rb +49 -0
  60. data/spec/analysis_specs/scope_spec.rb +20 -0
  61. data/spec/analysis_specs/sexp_analysis_spec.rb +134 -0
  62. data/spec/analysis_specs/spec_helper.rb +2 -0
  63. data/spec/analysis_specs/visitor_spec.rb +53 -0
  64. data/spec/analysis_specs/wool_class_spec.rb +54 -0
  65. data/spec/rake_specs/spec_helper.rb +1 -0
  66. data/spec/rake_specs/task_spec.rb +67 -0
  67. data/spec/runner_spec.rb +171 -0
  68. data/spec/scanner_spec.rb +75 -0
  69. data/spec/spec.opts +1 -0
  70. data/spec/spec_helper.rb +93 -0
  71. data/spec/support_specs/module_extensions_spec.rb +91 -0
  72. data/spec/support_specs/spec_helper.rb +1 -0
  73. data/spec/warning_spec.rb +95 -0
  74. data/spec/warning_specs/comment_spacing_spec.rb +57 -0
  75. data/spec/warning_specs/extra_blank_lines_spec.rb +70 -0
  76. data/spec/warning_specs/extra_whitespace_spec.rb +33 -0
  77. data/spec/warning_specs/line_length_spec.rb +165 -0
  78. data/spec/warning_specs/misaligned_unindentation_spec.rb +35 -0
  79. data/spec/warning_specs/operator_spacing_spec.rb +101 -0
  80. data/spec/warning_specs/rescue_exception_spec.rb +105 -0
  81. data/spec/warning_specs/semicolon_spec.rb +58 -0
  82. data/spec/warning_specs/spec_helper.rb +1 -0
  83. data/spec/warning_specs/useless_double_quotes_spec.rb +62 -0
  84. data/spec/wool_spec.rb +8 -0
  85. data/status_reports/2010/12/2010-12-14.md +163 -0
  86. data/test/third_party_tests/test_trollop.rb +1181 -0
  87. data/wool.gemspec +173 -0
  88. metadata +235 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## REDCAR
5
+ .redcar
6
+
7
+ ## TEXTMATE
8
+ *.tmproj
9
+ tmtags
10
+
11
+ ## EMACS
12
+ *~
13
+ \#*
14
+ .\#*
15
+
16
+ ## VIM
17
+ *.swp
18
+
19
+ ## PROJECT::GENERAL
20
+ coverage
21
+ rdoc
22
+ pkg
23
+
data/LICENSE ADDED
@@ -0,0 +1,45 @@
1
+ Copyright (c) 2009 Michael Edgar
2
+
3
+ Spirit of the License
4
+
5
+ This is an academic project, but I don't know where it will lead. I'm
6
+ licensing Wool this way in part to shield myself from liability (since
7
+ it rewrites code) and because it very well may have commercial applications
8
+ that I may wish to investigate.
9
+
10
+ Wool's License
11
+
12
+ Academic use only. No commercial or non-profit use. At least until
13
+ I know what I'm doing for sure. I couldn't find a legalese version of
14
+ an "academic use only" license, so I'll just write it again:
15
+
16
+ No non-academic use! No using Wool on code that will, at some point
17
+ in the near or distant future, produce monetary gain for you, your
18
+ relatives, your friends, your bosses, or anyone tangentially connected
19
+ to you. I don't care if you run it on legacy code that is defunct and
20
+ unused, but may have once derived you commercial gain. But don't run
21
+ it at your workplace. That's against the license.
22
+
23
+ Oh, and this important bit from the GPL applies as well:
24
+
25
+ NO WARRANTY
26
+
27
+ 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
28
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
29
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
30
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
31
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
32
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
33
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
34
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
35
+ REPAIR OR CORRECTION.
36
+
37
+ 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
38
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
39
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
40
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
41
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
42
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
43
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
44
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
45
+ POSSIBILITY OF SUCH DAMAGES.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = wool
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Michael Edgar. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,77 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "wool"
8
+ gem.summary = %Q{Analysis and Linting tool for Ruby.}
9
+ gem.description = %Q{Wool is an advanced static analysis tool for Ruby.}
10
+ gem.email = "michael.j.edgar@dartmouth.edu"
11
+ gem.homepage = "http://github.com/michaeledgar/wool"
12
+ gem.authors = ["Michael Edgar"]
13
+ gem.add_dependency "ruby_parser", ">= 2.0.5"
14
+ gem.add_dependency "ruby2ruby", ">= 1.2.4"
15
+ gem.add_development_dependency "rspec", ">= 1.2.9"
16
+ gem.add_development_dependency "yard", ">= 0"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ task :rcov => :default
31
+
32
+ require 'rake/testtask'
33
+ Rake::TestTask.new do |t|
34
+ t.libs << "test"
35
+ t.test_files = FileList['test/**/test*.rb']
36
+ t.verbose = true
37
+ end
38
+
39
+ require 'cucumber'
40
+ require 'cucumber/rake/task'
41
+
42
+ Cucumber::Rake::Task.new(:features) do |t|
43
+ t.cucumber_opts = "features --format pretty"
44
+ end
45
+
46
+ if true
47
+ begin
48
+ require 'wool'
49
+ Wool::Rake::WoolTask.new(:wool) do |wool|
50
+ wool.libs << 'lib' << 'spec'
51
+ wool.using << :all << Wool::LineLengthMaximum(100) << Wool::LineLengthWarning(80)
52
+ wool.options = '--debug --fix'
53
+ wool.fix << Wool::ExtraBlankLinesWarning << Wool::ExtraWhitespaceWarning << Wool::LineLengthWarning(80)
54
+ end
55
+ rescue LoadError => err
56
+ task :wool do
57
+ abort 'Wool is not available. In order to run wool, you must: sudo gem install wool'
58
+ end
59
+ end
60
+ end
61
+
62
+ task :rebuild => [:gemspec, :build, :install] do
63
+ %x(rake wool)
64
+ end
65
+
66
+ task :spec => :check_dependencies
67
+
68
+ task :default => [:spec, :test]
69
+
70
+ begin
71
+ require 'yard'
72
+ YARD::Rake::YardocTask.new
73
+ rescue LoadError
74
+ task :yardoc do
75
+ abort 'YARD is not available. In order to run yardoc, you must: sudo gem install yard'
76
+ end
77
+ end
data/TODO.md ADDED
@@ -0,0 +1,17 @@
1
+ # Cleanup
2
+
3
+ * Completely remove context stack remnants
4
+
5
+ # Useful Features
6
+
7
+ * Easy, DSL-like way to specify regex-matching, token-matching for match?
8
+ * Easy, DSL-like way to specify gsub-based fix calls
9
+ * Token Matching engine. Something along the lines of being able to match: [!whitespace, whitespace?, comment] to match code, possibly some whitespace, and a comment. Could donate to YARD.
10
+ * * DFA? Simple backtracking recursion?
11
+ * * Leftmost-longest
12
+
13
+ # Useful Warnings
14
+
15
+ * Assignment in condition
16
+ * Multiline blocks with { } notation
17
+ * Never-executed code
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.1
data/bin/wool ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'wool'
4
+ Wool::Runner.new(ARGV).run
@@ -0,0 +1,39 @@
1
+ TESTDATA_DIR = File.join(File.dirname(__FILE__), '..', 'support', 'testdata')
2
+
3
+ Given(/the following inputs and outputs:/) do |table|
4
+ @table = table
5
+ end
6
+
7
+ When(/I scan for warnings/) do
8
+ @result_table = [ ['input', 'output'] ]
9
+ swizzling_io do
10
+ @table.hashes.each do |hash|
11
+ full_path = File.join(TESTDATA_DIR, hash[:input])
12
+ runner = Wool::Scanner.new({})
13
+ warnings = runner.scan(File.read(full_path), hash[:input])
14
+ @result_table << [hash[:input], warnings.size.to_s]
15
+ end
16
+ end
17
+ end
18
+
19
+ Then(/the input and output tables should match/) do
20
+ @table.diff!(@result_table)
21
+ end
22
+
23
+ When(/I scan-and-fix warnings/) do
24
+ @result_table = [ ['input', 'output'] ]
25
+ swizzling_io do
26
+ @table.hashes.each do |hash|
27
+ full_path = File.join(TESTDATA_DIR, hash[:input])
28
+ expected_output = File.read(File.join(TESTDATA_DIR, hash[:output]))
29
+ captured_output = StringIO.new
30
+ Wool::LineLengthMaximum(80)
31
+ runner = Wool::Scanner.new(:fix => true, :output_file => captured_output)
32
+ runner.scan(File.read(full_path), hash[:input])
33
+ reported_output = hash[:output]
34
+ reported_output += '+' if captured_output.string != expected_output
35
+ STDERR.puts [captured_output.string, expected_output].inspect if captured_output.string != expected_output
36
+ @result_table << [hash[:input], reported_output]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'wool'
3
+
4
+ require 'spec/expectations'
5
+ require 'stringio'
6
+
7
+ def swizzling_io
8
+ old_stdout, $stdout = $stdout, StringIO.new
9
+ yield
10
+ $stdout.string
11
+ ensure
12
+ $stdout = old_stdout
13
+ end
14
+
@@ -0,0 +1 @@
1
+ a +b
@@ -0,0 +1 @@
1
+ a + b
@@ -0,0 +1,4 @@
1
+ def plus(a, b)
2
+ a+ b
3
+ b + a
4
+ end
@@ -0,0 +1,4 @@
1
+ def plus(a, b)
2
+ a + b
3
+ b + a
4
+ end
@@ -0,0 +1,8 @@
1
+ def plus(a, b)
2
+ a+ b
3
+ raise StandardError.new('a horrible thing happened and now we stopped') if x > 10 unless y
4
+ if some_test
5
+ do_something
6
+ end
7
+ end
8
+
@@ -0,0 +1,11 @@
1
+ def plus(a, b)
2
+ a + b
3
+ unless y
4
+ if x > 10
5
+ raise StandardError.new('a horrible thing happened and now we stopped')
6
+ end
7
+ end
8
+ if some_test
9
+ do_something
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ class SomeBigClass
2
+ attr_accessor :silly, :monkey
3
+
4
+ def xyz; 'xyz'; end # wool: ignore SemicolonWarning
5
+ end # wool: ignore MisalignedUnindentationWarning
@@ -0,0 +1,5 @@
1
+ class SomeBigClass
2
+ attr_accessor :silly, :monkey
3
+
4
+ def xyz; 'xyz'; end # wool: ignore SemicolonWarning
5
+ end # wool: ignore MisalignedUnindentationWarning
@@ -0,0 +1,24 @@
1
+ Feature: Find Warnings
2
+ In order to clean code
3
+ A user should be able to
4
+ scan for warnings
5
+
6
+ Scenario: Scan Single File
7
+ Given the following inputs and outputs:
8
+ | input | output |
9
+ | 1_input | 1 |
10
+ | 2_input | 4 |
11
+ | 3_input | 6 |
12
+ | 4_input | 3 |
13
+ When I scan for warnings
14
+ Then the input and output tables should match
15
+
16
+ Scenario: Scan-n-fix Single File
17
+ Given the following inputs and outputs:
18
+ | input | output |
19
+ | 1_input | 1_output |
20
+ | 2_input | 2_output |
21
+ | 3_input | 3_output |
22
+ | 4_input | 4_output |
23
+ When I scan-and-fix warnings
24
+ Then the input and output tables should match
data/lib/wool.rb ADDED
@@ -0,0 +1,40 @@
1
+ # Dependencies
2
+ require 'ripper'
3
+ require 'wool/third_party/trollop'
4
+ require 'wool/support/module_extensions'
5
+ require 'wool/advice/advice'
6
+ require 'wool/analysis/lexical_analysis'
7
+ require 'wool/analysis/sexp_analysis'
8
+ require 'wool/analysis/visitor'
9
+ require 'wool/analysis/symbol'
10
+ require 'wool/analysis/protocols'
11
+ require 'wool/analysis/signature'
12
+ require 'wool/analysis/wool_class'
13
+ require 'wool/analysis/protocol_registry'
14
+ require 'wool/analysis/scope'
15
+ require 'wool/analysis/annotations'
16
+ require 'wool/advice/comment_advice'
17
+
18
+ module Wool
19
+ # MOVE THIS
20
+ # TODO(adgar): move this to someplace effing sensible
21
+ def self.initialize_global_scope
22
+ object_class = SexpAnalysis::WoolClass.new('Object', nil)
23
+ global = SexpAnalysis::Scope.new(nil, object_class.class_object, {'Object' => object_class})
24
+ SexpAnalysis::Scope.const_set("GlobalScope", global) unless SexpAnalysis.const_defined?("GlobalScope")
25
+ object_class.instance_variable_set("@scope", SexpAnalysis::Scope::GlobalScope)
26
+ module_class = SexpAnalysis::WoolClass.new('Module')
27
+ module_class.superclass = object_class
28
+ end
29
+ initialize_global_scope
30
+ end
31
+ # Runners
32
+ require 'wool/runner'
33
+ require 'wool/rake/task'
34
+ # Program logic
35
+ require 'wool/warning'
36
+ require 'wool/scanner'
37
+
38
+ module Wool
39
+ VERSION = "0.5.0"
40
+ end
@@ -0,0 +1,42 @@
1
+ module Wool
2
+ # This module provides other modules the ability to add advice
3
+ # to methods. This module makes use of these functions opt-in.
4
+ module Advice
5
+ def advice_counter
6
+ @advice_counter ||= 0
7
+ end
8
+
9
+ def bump_advice_counter!
10
+ @advice_counter += 1
11
+ end
12
+
13
+ def before_advice(meth, advice)
14
+ with_advice(meth, :before => proc { send(advice) })
15
+ end
16
+
17
+ def after_advice(meth, advice)
18
+ with_advice(meth, :after => proc { send(advice) })
19
+ end
20
+
21
+ def argument_advice(meth, argument_tweaker)
22
+ with_advice(meth, :args => argument_tweaker)
23
+ end
24
+
25
+ def with_advice(meth, settings)
26
+ counter = advice_counter
27
+ alias_method "#{meth}_old#{counter}".to_sym, meth
28
+ define_method meth do |*args|
29
+ identity = proc {|*x| x}
30
+ instance_eval(&(settings[:before] || identity))
31
+ if settings[:args]
32
+ new_args = instance_eval(& proc { send(settings[:args], *args)})
33
+ end
34
+ result = send("#{meth}_old#{counter}", *new_args)
35
+ instance_eval(&(settings[:after] || identity))
36
+
37
+ result
38
+ end
39
+ bump_advice_counter!
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ module Wool
2
+ module Advice
3
+ # Using this module, you can make your match? method automatically
4
+ # receive de-commented source text.
5
+ #
6
+ # class MyWarning < Wool::Warning do
7
+ # extend Wool::Advice::CommentAdvice
8
+ #
9
+ # def self.match?(body, context)
10
+ # body.include?('#')
11
+ # end
12
+ # remove_comments
13
+ # end
14
+ module CommentAdvice
15
+ def self.included(klass)
16
+ klass.__send__(:extend, ClassMethods)
17
+ klass.__send__(:include, InstanceMethods)
18
+ end
19
+
20
+ module ClassMethods
21
+ def remove_comments
22
+ argument_advice :match?, :comment_removing_twiddler
23
+ end
24
+ end
25
+
26
+ module InstanceMethods
27
+ # This twiddler aims to remove comments and trailing whitespace
28
+ # from the ruby source input, so that warnings that aren't concerned
29
+ # with the implications of comments in their source can safely
30
+ # discard them. Uses Ripper to look for comment tokens.
31
+ def comment_removing_twiddler(body = self.body, settings = {})
32
+ [split_on_token(body, :on_comment).first.rstrip, settings]
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end