wool 0.5.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 (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