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.
- data/.document +5 -0
 - data/.gitignore +23 -0
 - data/LICENSE +45 -0
 - data/README.rdoc +17 -0
 - data/Rakefile +77 -0
 - data/TODO.md +17 -0
 - data/VERSION +1 -0
 - data/bin/wool +4 -0
 - data/features/step_definitions/wool_steps.rb +39 -0
 - data/features/support/env.rb +14 -0
 - data/features/support/testdata/1_input +1 -0
 - data/features/support/testdata/1_output +1 -0
 - data/features/support/testdata/2_input +4 -0
 - data/features/support/testdata/2_output +4 -0
 - data/features/support/testdata/3_input +8 -0
 - data/features/support/testdata/3_output +11 -0
 - data/features/support/testdata/4_input +5 -0
 - data/features/support/testdata/4_output +5 -0
 - data/features/wool.feature +24 -0
 - data/lib/wool.rb +40 -0
 - data/lib/wool/advice/advice.rb +42 -0
 - data/lib/wool/advice/comment_advice.rb +37 -0
 - data/lib/wool/analysis/annotations.rb +34 -0
 - data/lib/wool/analysis/annotations/next_annotation.rb +26 -0
 - data/lib/wool/analysis/annotations/parent_annotation.rb +20 -0
 - data/lib/wool/analysis/annotations/scope_annotation.rb +37 -0
 - data/lib/wool/analysis/lexical_analysis.rb +165 -0
 - data/lib/wool/analysis/protocol_registry.rb +32 -0
 - data/lib/wool/analysis/protocols.rb +82 -0
 - data/lib/wool/analysis/scope.rb +13 -0
 - data/lib/wool/analysis/sexp_analysis.rb +98 -0
 - data/lib/wool/analysis/signature.rb +16 -0
 - data/lib/wool/analysis/symbol.rb +10 -0
 - data/lib/wool/analysis/visitor.rb +36 -0
 - data/lib/wool/analysis/wool_class.rb +47 -0
 - data/lib/wool/rake/task.rb +42 -0
 - data/lib/wool/runner.rb +156 -0
 - data/lib/wool/scanner.rb +160 -0
 - data/lib/wool/support/module_extensions.rb +84 -0
 - data/lib/wool/third_party/trollop.rb +845 -0
 - data/lib/wool/warning.rb +145 -0
 - data/lib/wool/warnings/comment_spacing.rb +30 -0
 - data/lib/wool/warnings/extra_blank_lines.rb +29 -0
 - data/lib/wool/warnings/extra_whitespace.rb +15 -0
 - data/lib/wool/warnings/line_length.rb +113 -0
 - data/lib/wool/warnings/misaligned_unindentation.rb +16 -0
 - data/lib/wool/warnings/operator_spacing.rb +63 -0
 - data/lib/wool/warnings/rescue_exception.rb +41 -0
 - data/lib/wool/warnings/semicolon.rb +24 -0
 - data/lib/wool/warnings/useless_double_quotes.rb +37 -0
 - data/spec/advice_specs/advice_spec.rb +69 -0
 - data/spec/advice_specs/comment_advice_spec.rb +38 -0
 - data/spec/advice_specs/spec_helper.rb +1 -0
 - data/spec/analysis_specs/annotations_specs/next_prev_annotation_spec.rb +47 -0
 - data/spec/analysis_specs/annotations_specs/parent_annotation_spec.rb +41 -0
 - data/spec/analysis_specs/annotations_specs/spec_helper.rb +5 -0
 - data/spec/analysis_specs/lexical_analysis_spec.rb +179 -0
 - data/spec/analysis_specs/protocol_registry_spec.rb +58 -0
 - data/spec/analysis_specs/protocols_spec.rb +49 -0
 - data/spec/analysis_specs/scope_spec.rb +20 -0
 - data/spec/analysis_specs/sexp_analysis_spec.rb +134 -0
 - data/spec/analysis_specs/spec_helper.rb +2 -0
 - data/spec/analysis_specs/visitor_spec.rb +53 -0
 - data/spec/analysis_specs/wool_class_spec.rb +54 -0
 - data/spec/rake_specs/spec_helper.rb +1 -0
 - data/spec/rake_specs/task_spec.rb +67 -0
 - data/spec/runner_spec.rb +171 -0
 - data/spec/scanner_spec.rb +75 -0
 - data/spec/spec.opts +1 -0
 - data/spec/spec_helper.rb +93 -0
 - data/spec/support_specs/module_extensions_spec.rb +91 -0
 - data/spec/support_specs/spec_helper.rb +1 -0
 - data/spec/warning_spec.rb +95 -0
 - data/spec/warning_specs/comment_spacing_spec.rb +57 -0
 - data/spec/warning_specs/extra_blank_lines_spec.rb +70 -0
 - data/spec/warning_specs/extra_whitespace_spec.rb +33 -0
 - data/spec/warning_specs/line_length_spec.rb +165 -0
 - data/spec/warning_specs/misaligned_unindentation_spec.rb +35 -0
 - data/spec/warning_specs/operator_spacing_spec.rb +101 -0
 - data/spec/warning_specs/rescue_exception_spec.rb +105 -0
 - data/spec/warning_specs/semicolon_spec.rb +58 -0
 - data/spec/warning_specs/spec_helper.rb +1 -0
 - data/spec/warning_specs/useless_double_quotes_spec.rb +62 -0
 - data/spec/wool_spec.rb +8 -0
 - data/status_reports/2010/12/2010-12-14.md +163 -0
 - data/test/third_party_tests/test_trollop.rb +1181 -0
 - data/wool.gemspec +173 -0
 - metadata +235 -0
 
| 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Wool
         
     | 
| 
      
 2 
     | 
    
         
            +
              module SexpAnalysis
         
     | 
| 
      
 3 
     | 
    
         
            +
                # A single signature in the Wool protocol system. This is just
         
     | 
| 
      
 4 
     | 
    
         
            +
                # a simple specification of a method that an object can receive,
         
     | 
| 
      
 5 
     | 
    
         
            +
                # either explicitly or implicitly defined, and the protocols of the
         
     | 
| 
      
 6 
     | 
    
         
            +
                # return type and all arguments.
         
     | 
| 
      
 7 
     | 
    
         
            +
                class Signature < Struct.new(:name, :return_protocol, :argument_protocols)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  include Comparable
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def <=>(other)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    [self.name, self.return_protocol, self.argument_protocols] <=>
         
     | 
| 
      
 12 
     | 
    
         
            +
                        [other.name, other.return_protocol, other.argument_protocols]
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,10 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Wool
         
     | 
| 
      
 2 
     | 
    
         
            +
              module SexpAnalysis
         
     | 
| 
      
 3 
     | 
    
         
            +
                # This class represents a Symbol in Ruby. It may have a known protocol (type),
         
     | 
| 
      
 4 
     | 
    
         
            +
                # class, value (if constant!), and a variety of other details.
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Symbol < Struct.new(:protocol, :class_used, :value, :scope, :name)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  include Comparable
         
     | 
| 
      
 7 
     | 
    
         
            +
                  
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Wool
         
     | 
| 
      
 2 
     | 
    
         
            +
              module SexpAnalysis
         
     | 
| 
      
 3 
     | 
    
         
            +
                # Visitor: a set of methods for visiting an AST. The
         
     | 
| 
      
 4 
     | 
    
         
            +
                # default implementations visit each child and do no
         
     | 
| 
      
 5 
     | 
    
         
            +
                # other processing. By including this module, and
         
     | 
| 
      
 6 
     | 
    
         
            +
                # implementing certain methods, you can do your own
         
     | 
| 
      
 7 
     | 
    
         
            +
                # processing on, say, every instance of a :rescue AST node.
         
     | 
| 
      
 8 
     | 
    
         
            +
                # The default implementation will go arbitrarily deep in the AST
         
     | 
| 
      
 9 
     | 
    
         
            +
                # tree until it hits a method you define.
         
     | 
| 
      
 10 
     | 
    
         
            +
                module Visitor
         
     | 
| 
      
 11 
     | 
    
         
            +
                  def visit(node)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    case node
         
     | 
| 
      
 13 
     | 
    
         
            +
                    when Sexp
         
     | 
| 
      
 14 
     | 
    
         
            +
                      case node[0]
         
     | 
| 
      
 15 
     | 
    
         
            +
                      when ::Symbol
         
     | 
| 
      
 16 
     | 
    
         
            +
                        send("visit_#{node[0]}", node)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      when Array
         
     | 
| 
      
 18 
     | 
    
         
            +
                        node.each {|x| visit(x)}
         
     | 
| 
      
 19 
     | 
    
         
            +
                      end
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                  
         
     | 
| 
      
 23 
     | 
    
         
            +
                  def default_visit(node)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    node.children.select {|x| Sexp === x}.each {|x| visit(x) }
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                  
         
     | 
| 
      
 27 
     | 
    
         
            +
                  def method_missing(meth, *args, &blk)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    if meth.to_s[0,6] == 'visit_'
         
     | 
| 
      
 29 
     | 
    
         
            +
                      default_visit args.first
         
     | 
| 
      
 30 
     | 
    
         
            +
                    else
         
     | 
| 
      
 31 
     | 
    
         
            +
                      raise
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Wool
         
     | 
| 
      
 2 
     | 
    
         
            +
              module SexpAnalysis
         
     | 
| 
      
 3 
     | 
    
         
            +
                # Wool representation of a class. I named it WoolClass so it wouldn't
         
     | 
| 
      
 4 
     | 
    
         
            +
                # clash with regular Class. This links the class to its protocol. It
         
     | 
| 
      
 5 
     | 
    
         
            +
                # has lists of methods, instance variables, and so on.
         
     | 
| 
      
 6 
     | 
    
         
            +
                class WoolClass
         
     | 
| 
      
 7 
     | 
    
         
            +
                  attr_reader :path, :methods, :protocol, :scope, :class_object
         
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_accessor :superclass
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize(full_path, scope = Scope::GlobalScope)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @path = full_path
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @methods = {}
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @protocol = Protocols::ClassProtocol.new(self)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @scope = scope
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @class_object = Symbol.new(@protocol, self)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    ProtocolRegistry.add_class_protocol(@protocol)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    yield self if block_given?
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  
         
     | 
| 
      
 20 
     | 
    
         
            +
                  def add_method(method)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @methods[method.name] = method
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  
         
     | 
| 
      
 24 
     | 
    
         
            +
                  def signatures
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @methods.values.map(&:signatures).flatten
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
                
         
     | 
| 
      
 29 
     | 
    
         
            +
                # Wool representation of a method. This name is tweaked so it doesn't
         
     | 
| 
      
 30 
     | 
    
         
            +
                # collide with ::Method.
         
     | 
| 
      
 31 
     | 
    
         
            +
                class WoolMethod
         
     | 
| 
      
 32 
     | 
    
         
            +
                  extend ModuleExtensions
         
     | 
| 
      
 33 
     | 
    
         
            +
                  attr_reader :name, :signatures
         
     | 
| 
      
 34 
     | 
    
         
            +
                  attr_accessor_with_default :pure, false
         
     | 
| 
      
 35 
     | 
    
         
            +
                  
         
     | 
| 
      
 36 
     | 
    
         
            +
                  def initialize(name)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    @name = name
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @signatures = []
         
     | 
| 
      
 39 
     | 
    
         
            +
                    yield self if block_given?
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  
         
     | 
| 
      
 42 
     | 
    
         
            +
                  def add_signature(return_proto, arg_protos)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @signatures << Signature.new(self.name, return_proto, arg_protos)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Wool
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Rake
         
     | 
| 
      
 3 
     | 
    
         
            +
                class WoolTask
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class Settings < Struct.new(:libs, :extras, :options, :using, :fix)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    def initialize(*args)
         
     | 
| 
      
 6 
     | 
    
         
            +
                      super
         
     | 
| 
      
 7 
     | 
    
         
            +
                      self.libs ||= []
         
     | 
| 
      
 8 
     | 
    
         
            +
                      self.extras ||= []
         
     | 
| 
      
 9 
     | 
    
         
            +
                      self.options ||= ''
         
     | 
| 
      
 10 
     | 
    
         
            +
                      self.using ||= []
         
     | 
| 
      
 11 
     | 
    
         
            +
                      self.fix ||= []
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_accessor :settings
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def initialize(task_name)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @settings = Settings.new
         
     | 
| 
      
 19 
     | 
    
         
            +
                    yield @settings if block_given?
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @settings.using = [:all] if @settings.using.empty?
         
     | 
| 
      
 21 
     | 
    
         
            +
                    task task_name do
         
     | 
| 
      
 22 
     | 
    
         
            +
                      run
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def run
         
     | 
| 
      
 27 
     | 
    
         
            +
                    files = []
         
     | 
| 
      
 28 
     | 
    
         
            +
                    if @settings.libs.any?
         
     | 
| 
      
 29 
     | 
    
         
            +
                      @settings.libs.each do |lib|
         
     | 
| 
      
 30 
     | 
    
         
            +
                        Dir["#{lib}/**/*.rb"].each do |file|
         
     | 
| 
      
 31 
     | 
    
         
            +
                          files << file
         
     | 
| 
      
 32 
     | 
    
         
            +
                        end
         
     | 
| 
      
 33 
     | 
    
         
            +
                      end
         
     | 
| 
      
 34 
     | 
    
         
            +
                    end
         
     | 
| 
      
 35 
     | 
    
         
            +
                    runner = Wool::Runner.new(self.settings.options.split(/\s/) + files)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    runner.using = self.settings.using
         
     | 
| 
      
 37 
     | 
    
         
            +
                    runner.fix = self.settings.fix
         
     | 
| 
      
 38 
     | 
    
         
            +
                    runner.run
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/wool/runner.rb
    ADDED
    
    | 
         @@ -0,0 +1,156 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Wool
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Runner
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :using, :fix
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(argv)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @argv = argv
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @using = [:all]
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @fix = [:all]
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def run
         
     | 
| 
      
 12 
     | 
    
         
            +
                  settings, files = collect_options_and_arguments
         
     | 
| 
      
 13 
     | 
    
         
            +
                  settings[:__using__] = warnings_to_consider
         
     | 
| 
      
 14 
     | 
    
         
            +
                  settings[:__fix__] = warnings_to_fix
         
     | 
| 
      
 15 
     | 
    
         
            +
                  scanner = Scanner.new(settings)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  warnings = collect_warnings(files, scanner)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  display_warnings(warnings, settings) if settings[:display]
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def collect_options_and_arguments
         
     | 
| 
      
 21 
     | 
    
         
            +
                  swizzling_argv do
         
     | 
| 
      
 22 
     | 
    
         
            +
                    settings = get_settings
         
     | 
| 
      
 23 
     | 
    
         
            +
                    handle_global_options(settings)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    p settings if settings[:debug]
         
     | 
| 
      
 25 
     | 
    
         
            +
                    files = ARGV.dup
         
     | 
| 
      
 26 
     | 
    
         
            +
                    [settings, files]
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
                
         
     | 
| 
      
 30 
     | 
    
         
            +
                # Processes the global options, which includes picking which warnings to
         
     | 
| 
      
 31 
     | 
    
         
            +
                # run against the source code. The settings provided determine what
         
     | 
| 
      
 32 
     | 
    
         
            +
                # modifies the runner's settings.
         
     | 
| 
      
 33 
     | 
    
         
            +
                #
         
     | 
| 
      
 34 
     | 
    
         
            +
                # @param [Hash] settings the settings from the command-line to process.
         
     | 
| 
      
 35 
     | 
    
         
            +
                # @option settings :only (String) a list of warning names or short names
         
     | 
| 
      
 36 
     | 
    
         
            +
                #   that will be the only warnings run. The names should be whitespace-delimited.
         
     | 
| 
      
 37 
     | 
    
         
            +
                # @option settings :"line-length" (Integer) a maximum line length to
         
     | 
| 
      
 38 
     | 
    
         
            +
                #   generate a warning for. A common choice is 80/83.
         
     | 
| 
      
 39 
     | 
    
         
            +
                def handle_global_options(settings)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  if settings[:"line-length"]
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @using << Wool.LineLengthWarning(settings[:"line-length"])
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  if (only_name = settings[:only])
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @fix = @using = Warning.concrete_warnings.select do |w|
         
     | 
| 
      
 45 
     | 
    
         
            +
                      (w.name && w.name.index(only_name)) || (w.short_name && only_name.index(w.short_name))
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                  ARGV.replace(['(stdin)']) if settings[:stdin]
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                # Parses the command-line options using Trollop
         
     | 
| 
      
 52 
     | 
    
         
            +
                #
         
     | 
| 
      
 53 
     | 
    
         
            +
                # @return [Hash{Symbol => Object}] the settings entered by the user
         
     | 
| 
      
 54 
     | 
    
         
            +
                def get_settings
         
     | 
| 
      
 55 
     | 
    
         
            +
                  warning_opts = get_warning_options
         
     | 
| 
      
 56 
     | 
    
         
            +
                  Trollop::options do
         
     | 
| 
      
 57 
     | 
    
         
            +
                    banner 'Ask Peeves - the Ruby Linter'
         
     | 
| 
      
 58 
     | 
    
         
            +
                    opt :fix, 'Should errors be fixed in-line?', :short => '-f'
         
     | 
| 
      
 59 
     | 
    
         
            +
                    opt :display, 'Should errors be displayed?', :short => '-b', :default => true
         
     | 
| 
      
 60 
     | 
    
         
            +
                    opt :"report-fixed", 'Should fixed errors be reported anyway?', :short => '-r'
         
     | 
| 
      
 61 
     | 
    
         
            +
                    opt :"line-length", 'Warn at the given line length', :short => '-l', :type => :int
         
     | 
| 
      
 62 
     | 
    
         
            +
                    opt :only, 'Only consider the given warning (by short or full name)', :short => '-O', :type => :string
         
     | 
| 
      
 63 
     | 
    
         
            +
                    opt :stdin, 'Read Ruby code from standard input', :short => '-s'
         
     | 
| 
      
 64 
     | 
    
         
            +
                    warning_opts.each { |warning| opt(*warning) }
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                # Gets all the options from the warning plugins and collects them
         
     | 
| 
      
 69 
     | 
    
         
            +
                # with overriding rules. The later the declaration is run, the higher the
         
     | 
| 
      
 70 
     | 
    
         
            +
                # priority the option has.
         
     | 
| 
      
 71 
     | 
    
         
            +
                def get_warning_options
         
     | 
| 
      
 72 
     | 
    
         
            +
                  all_options = Warning.all_warnings.inject({}) do |result, warning|
         
     | 
| 
      
 73 
     | 
    
         
            +
                    options = warning.options
         
     | 
| 
      
 74 
     | 
    
         
            +
                    options = [options] if options.any? && !options[0].is_a?(Array)
         
     | 
| 
      
 75 
     | 
    
         
            +
                    options.each do |option|
         
     | 
| 
      
 76 
     | 
    
         
            +
                      result[option.first] = option
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                    result
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
                  all_options.values
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                # Converts a list of warnings and symbol shortcuts for warnings to just a
         
     | 
| 
      
 84 
     | 
    
         
            +
                # list of warnings.
         
     | 
| 
      
 85 
     | 
    
         
            +
                def convert_warning_list(list)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  list.map do |list|
         
     | 
| 
      
 87 
     | 
    
         
            +
                    case list
         
     | 
| 
      
 88 
     | 
    
         
            +
                    when :all then Warning.all_warnings
         
     | 
| 
      
 89 
     | 
    
         
            +
                    when :whitespace
         
     | 
| 
      
 90 
     | 
    
         
            +
                      [ExtraBlankLinesWarning, ExtraWhitespaceWarning,
         
     | 
| 
      
 91 
     | 
    
         
            +
                       OperatorSpacing, MisalignedUnindentationWarning]
         
     | 
| 
      
 92 
     | 
    
         
            +
                      else list
         
     | 
| 
      
 93 
     | 
    
         
            +
                    end
         
     | 
| 
      
 94 
     | 
    
         
            +
                  end.flatten
         
     | 
| 
      
 95 
     | 
    
         
            +
                end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                # Returns the list of warnings the user has activated for use.
         
     | 
| 
      
 98 
     | 
    
         
            +
                def warnings_to_consider
         
     | 
| 
      
 99 
     | 
    
         
            +
                  convert_warning_list(@using)
         
     | 
| 
      
 100 
     | 
    
         
            +
                end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                # Returns the list of warnings the user has selected for fixing
         
     | 
| 
      
 103 
     | 
    
         
            +
                def warnings_to_fix
         
     | 
| 
      
 104 
     | 
    
         
            +
                  convert_warning_list(@fix)
         
     | 
| 
      
 105 
     | 
    
         
            +
                end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                # Sets the ARGV variable to the runner's arguments during the execution
         
     | 
| 
      
 108 
     | 
    
         
            +
                # of the block.
         
     | 
| 
      
 109 
     | 
    
         
            +
                def swizzling_argv
         
     | 
| 
      
 110 
     | 
    
         
            +
                  old_argv = ARGV.dup
         
     | 
| 
      
 111 
     | 
    
         
            +
                  ARGV.replace @argv
         
     | 
| 
      
 112 
     | 
    
         
            +
                  yield
         
     | 
| 
      
 113 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 114 
     | 
    
         
            +
                  ARGV.replace old_argv
         
     | 
| 
      
 115 
     | 
    
         
            +
                end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                # Collects warnings from all the provided files by running them through
         
     | 
| 
      
 118 
     | 
    
         
            +
                # the scanner.
         
     | 
| 
      
 119 
     | 
    
         
            +
                #
         
     | 
| 
      
 120 
     | 
    
         
            +
                # @param [Array<String>] files the files to scan. If (stdin) is in the
         
     | 
| 
      
 121 
     | 
    
         
            +
                #   array, then data will be read from STDIN until EOF is reached.
         
     | 
| 
      
 122 
     | 
    
         
            +
                # @param [Scanner] scanner the scanner that will look for warnings
         
     | 
| 
      
 123 
     | 
    
         
            +
                #   in the source text.
         
     | 
| 
      
 124 
     | 
    
         
            +
                # @return [Array<Warning>] a set of warnings, ordered by file.
         
     | 
| 
      
 125 
     | 
    
         
            +
                def collect_warnings(files, scanner)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  full_list = files.map do |file|
         
     | 
| 
      
 127 
     | 
    
         
            +
                    data = file == '(stdin)' ? STDIN.read : File.read(file)
         
     | 
| 
      
 128 
     | 
    
         
            +
                    if scanner.settings[:fix]
         
     | 
| 
      
 129 
     | 
    
         
            +
                      scanner.settings[:output_file] = scanner.settings[:stdin] ? STDOUT : File.open(file, 'w')
         
     | 
| 
      
 130 
     | 
    
         
            +
                    end
         
     | 
| 
      
 131 
     | 
    
         
            +
                    results = scanner.scan(data, file)
         
     | 
| 
      
 132 
     | 
    
         
            +
                    scanner.settings[:output_file].close if scanner.settings[:fix] && !scanner.settings[:stdin]
         
     | 
| 
      
 133 
     | 
    
         
            +
                    results
         
     | 
| 
      
 134 
     | 
    
         
            +
                  end
         
     | 
| 
      
 135 
     | 
    
         
            +
                  full_list.flatten
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                # Displays warnings using user-provided settings.
         
     | 
| 
      
 139 
     | 
    
         
            +
                #
         
     | 
| 
      
 140 
     | 
    
         
            +
                # @param [Array<Warning>] warnings the warnings generated by the input
         
     | 
| 
      
 141 
     | 
    
         
            +
                #   files, ordered by file
         
     | 
| 
      
 142 
     | 
    
         
            +
                # @param [Hash{Symbol => Object}] settings the user-set display settings
         
     | 
| 
      
 143 
     | 
    
         
            +
                def display_warnings(warnings, settings)
         
     | 
| 
      
 144 
     | 
    
         
            +
                  num_fixable = warnings.select { |warn| warn.fixable? }.size
         
     | 
| 
      
 145 
     | 
    
         
            +
                  num_total = warnings.size
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                  results = "#{num_total} warnings found. #{num_fixable} are fixable."
         
     | 
| 
      
 148 
     | 
    
         
            +
                  puts results
         
     | 
| 
      
 149 
     | 
    
         
            +
                  puts '=' * results.size
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                  warnings.each do |warning|
         
     | 
| 
      
 152 
     | 
    
         
            +
                    puts "#{warning.file}:#{warning.line_number} #{warning.name} (#{warning.severity}) - #{warning.desc}"
         
     | 
| 
      
 153 
     | 
    
         
            +
                  end
         
     | 
| 
      
 154 
     | 
    
         
            +
                end
         
     | 
| 
      
 155 
     | 
    
         
            +
              end
         
     | 
| 
      
 156 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/wool/scanner.rb
    ADDED
    
    | 
         @@ -0,0 +1,160 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Wool
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Scanner
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :settings
         
     | 
| 
      
 4 
     | 
    
         
            +
                attr_accessor :indent_stack
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                DEFAULT_SETTINGS = {:fix => false, :output => STDOUT, :indent_size => 2,
         
     | 
| 
      
 7 
     | 
    
         
            +
                                    :__using__ => Wool::Warning.all_warnings,
         
     | 
| 
      
 8 
     | 
    
         
            +
                                    :__fix__ => Wool::Warning.all_warnings}
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # Initializes the scanner with the given settings
         
     | 
| 
      
 11 
     | 
    
         
            +
                #
         
     | 
| 
      
 12 
     | 
    
         
            +
                # @param [Hash] settings the settings to use to customize this scanner's
         
     | 
| 
      
 13 
     | 
    
         
            +
                #   scanning behavior
         
     | 
| 
      
 14 
     | 
    
         
            +
                def initialize(settings = DEFAULT_SETTINGS)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @settings = DEFAULT_SETTINGS.merge(settings)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @settings[:__scanner__] = self
         
     | 
| 
      
 17 
     | 
    
         
            +
                  self.indent_stack = []
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                # Returns the list of warnings to use for scanning.
         
     | 
| 
      
 21 
     | 
    
         
            +
                def using
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @settings[:__using__]
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                # Should we use this warning?
         
     | 
| 
      
 26 
     | 
    
         
            +
                def using?(warning)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @settings[:__using__].include? warning
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                # Returns the list of warnings to use for scanning.
         
     | 
| 
      
 31 
     | 
    
         
            +
                def fix
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @settings[:__fix__]
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                # Should we use this warning?
         
     | 
| 
      
 36 
     | 
    
         
            +
                def fixing?(warning)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @settings[:__fix__].include? warning.class
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                # Scans the text for warnings.
         
     | 
| 
      
 41 
     | 
    
         
            +
                #
         
     | 
| 
      
 42 
     | 
    
         
            +
                # @param [String] text the input ruby file to scan
         
     | 
| 
      
 43 
     | 
    
         
            +
                # @return [Array[Wool::Warnings]] the warnings generated by the code.
         
     | 
| 
      
 44 
     | 
    
         
            +
                #   If the code is clean, an empty array is returned.
         
     | 
| 
      
 45 
     | 
    
         
            +
                def scan(text, filename='(none)')
         
     | 
| 
      
 46 
     | 
    
         
            +
                  warnings = scan_for_file_warnings(text, filename)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  text = filter_fixable(warnings).inject(text) do |text, warning|
         
     | 
| 
      
 48 
     | 
    
         
            +
                    warning.fix(text)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                  with_fixing_piped_to_output do
         
     | 
| 
      
 51 
     | 
    
         
            +
                    text.split(/\n/).each_with_index do |line, number|
         
     | 
| 
      
 52 
     | 
    
         
            +
                      warnings.concat process_line(line, number + 1, filename)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  warnings
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def with_fixing_piped_to_output
         
     | 
| 
      
 59 
     | 
    
         
            +
                  self.settings[:output_lines] = []
         
     | 
| 
      
 60 
     | 
    
         
            +
                  yield
         
     | 
| 
      
 61 
     | 
    
         
            +
                  if @settings[:fix]
         
     | 
| 
      
 62 
     | 
    
         
            +
                    self.settings[:output_file].write self.settings[:output_lines].join("\n")
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                # Finds all matching warnings, and if the user wishes, fix a subset of them.
         
     | 
| 
      
 67 
     | 
    
         
            +
                def process_line(line, line_number, filename)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  warnings = all_warnings_for_line(line, line_number, filename)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  fix_input(warnings, line, line_number, filename) if @settings[:fix]
         
     | 
| 
      
 70 
     | 
    
         
            +
                  warnings
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                # Tries to fix the given line with a set of matching warnings for that line.
         
     | 
| 
      
 74 
     | 
    
         
            +
                # May recurse if there are multiple warnings on the same line.
         
     | 
| 
      
 75 
     | 
    
         
            +
                def fix_input(warnings, line, line_number, filename)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  fixable_warnings = filter_fixable warnings
         
     | 
| 
      
 77 
     | 
    
         
            +
                  if fixable_warnings.size == 1
         
     | 
| 
      
 78 
     | 
    
         
            +
                    self.settings[:output_lines] << fixable_warnings.first.fix rescue line
         
     | 
| 
      
 79 
     | 
    
         
            +
                  elsif fixable_warnings.size > 1
         
     | 
| 
      
 80 
     | 
    
         
            +
                    new_text = fixable_warnings.first.fix rescue line
         
     | 
| 
      
 81 
     | 
    
         
            +
                    process_line(new_text, line_number, filename)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  else
         
     | 
| 
      
 83 
     | 
    
         
            +
                    self.settings[:output_lines] << line
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                # Returns all warnings that match the line
         
     | 
| 
      
 88 
     | 
    
         
            +
                def all_warnings_for_line(line, line_number, filename)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  new_warnings = check_for_indent_warnings!(line, filename)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  new_warnings.concat scan_for_line_warnings(line, filename)
         
     | 
| 
      
 91 
     | 
    
         
            +
                  new_warnings.each {|warning| warning.line_number = line_number}
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                # Returns only the warnings that we should fix
         
     | 
| 
      
 95 
     | 
    
         
            +
                def filter_fixable(warnings)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  warnings.select {|warning| warning.fixable? && fixing?(warning) }
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                # Checks for new warnings based on indentation.
         
     | 
| 
      
 100 
     | 
    
         
            +
                def check_for_indent_warnings!(line, filename)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  return [] if line == ""
         
     | 
| 
      
 102 
     | 
    
         
            +
                  indent_size = get_indent_size line
         
     | 
| 
      
 103 
     | 
    
         
            +
                  if indent_size > current_indent
         
     | 
| 
      
 104 
     | 
    
         
            +
                    self.indent_stack.push indent_size
         
     | 
| 
      
 105 
     | 
    
         
            +
                  elsif indent_size < current_indent
         
     | 
| 
      
 106 
     | 
    
         
            +
                    previous = self.indent_stack.pop
         
     | 
| 
      
 107 
     | 
    
         
            +
                    if indent_size != current_indent &&
         
     | 
| 
      
 108 
     | 
    
         
            +
                       using.include?(MisalignedUnindentationWarning)
         
     | 
| 
      
 109 
     | 
    
         
            +
                      warnings_to_check = [MisalignedUnindentationWarning.new(filename, line, current_indent)]
         
     | 
| 
      
 110 
     | 
    
         
            +
                      return filtered_warnings_from_line(line, warnings_to_check)
         
     | 
| 
      
 111 
     | 
    
         
            +
                    end
         
     | 
| 
      
 112 
     | 
    
         
            +
                  end
         
     | 
| 
      
 113 
     | 
    
         
            +
                  []
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                # Gets the current indent size
         
     | 
| 
      
 117 
     | 
    
         
            +
                def current_indent
         
     | 
| 
      
 118 
     | 
    
         
            +
                  self.indent_stack.last || 0
         
     | 
| 
      
 119 
     | 
    
         
            +
                end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                # Gets the indent size of a given line
         
     | 
| 
      
 122 
     | 
    
         
            +
                def get_indent_size(line)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  line.match(/^\s*/)[0].size
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                # Goes through all file warning subclasses and see what warnings the file
         
     | 
| 
      
 127 
     | 
    
         
            +
                # generates as a whole.
         
     | 
| 
      
 128 
     | 
    
         
            +
                def scan_for_file_warnings(file, filename)
         
     | 
| 
      
 129 
     | 
    
         
            +
                  scan_for_warnings(using & FileWarning.all_warnings, file, filename)
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                # Goes through all line warning subclasses and checks if we got some new
         
     | 
| 
      
 133 
     | 
    
         
            +
                # warnings for a given line
         
     | 
| 
      
 134 
     | 
    
         
            +
                def scan_for_line_warnings(line, filename)
         
     | 
| 
      
 135 
     | 
    
         
            +
                  warnings = scan_for_warnings(using & LineWarning.all_warnings, line, filename)
         
     | 
| 
      
 136 
     | 
    
         
            +
                  filtered_warnings_from_line(line, warnings)
         
     | 
| 
      
 137 
     | 
    
         
            +
                end
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                private
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                # Filters the list of warnings by checking the line for warnings to
         
     | 
| 
      
 142 
     | 
    
         
            +
                # ignore. The line should contain "wool: ignore ClassToIgnore" in a comment,
         
     | 
| 
      
 143 
     | 
    
         
            +
                # though you can omit the space between "wool:" and "ignore".
         
     | 
| 
      
 144 
     | 
    
         
            +
                def filtered_warnings_from_line(line, warnings)
         
     | 
| 
      
 145 
     | 
    
         
            +
                  match = line.match(/#.*wool:\s*ignore\s+(.*)$/)
         
     | 
| 
      
 146 
     | 
    
         
            +
                  return warnings unless match && ignore_label = match[1]
         
     | 
| 
      
 147 
     | 
    
         
            +
                  class_names = ignore_label.split
         
     | 
| 
      
 148 
     | 
    
         
            +
                  result = warnings.reject do |warning|
         
     | 
| 
      
 149 
     | 
    
         
            +
                    class_names.include?(warning.class.name.gsub(/.*::(.*)/, '\1')) ||
         
     | 
| 
      
 150 
     | 
    
         
            +
                      class_names.include?(warning.class.short_name)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  end
         
     | 
| 
      
 152 
     | 
    
         
            +
                  result
         
     | 
| 
      
 153 
     | 
    
         
            +
                end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                def scan_for_warnings(warnings, content, filename)
         
     | 
| 
      
 156 
     | 
    
         
            +
                  warnings.map! { |warning| warning.new(filename, content, @settings)}
         
     | 
| 
      
 157 
     | 
    
         
            +
                  warnings.map { |warning| warning.generated_warnings(warning.body)}.flatten.uniq
         
     | 
| 
      
 158 
     | 
    
         
            +
                end
         
     | 
| 
      
 159 
     | 
    
         
            +
              end
         
     | 
| 
      
 160 
     | 
    
         
            +
            end
         
     |