Permalink
Browse files

Initial Commit

  • Loading branch information...
0 parents commit 645bb0847b2b9569b304e3c84df99dff7f17d695 @ericallam ericallam committed May 26, 2011
5 .gitignore
@@ -0,0 +1,5 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
+.rvmrc
4 Gemfile
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in ruby_cop.gemspec
+gemspec
0 README
No changes.
2 Rakefile
@@ -0,0 +1,2 @@
+require 'bundler'
+Bundler::GemHelper.install_tasks
12 lib/ruby_cop.rb
@@ -0,0 +1,12 @@
+module RubyCop
+ # Your code goes here...
+end
+
+require 'ripper'
+
+require 'ruby_cop/ruby'
+require 'ruby_cop/node_builder'
+require 'ruby_cop/gray_list'
+require 'ruby_cop/policy'
+
+
26 lib/ruby_cop/gray_list.rb
@@ -0,0 +1,26 @@
+require 'set'
+
+module RubyCop
+ # Combination blacklist and whitelist.
+ class GrayList
+ def initialize
+ @blacklist = Set.new
+ @whitelist = Set.new
+ end
+
+ # An item is allowed if it's whitelisted, or if it's not blacklisted.
+ def allow?(item)
+ @whitelist.include?(item) || !@blacklist.include?(item)
+ end
+
+ def blacklist(item)
+ @whitelist.delete(item)
+ @blacklist.add(item)
+ end
+
+ def whitelist(item)
+ @blacklist.delete(item)
+ @whitelist.add(item)
+ end
+ end
+end
521 lib/ruby_cop/node_builder.rb
@@ -0,0 +1,521 @@
+module RubyCop
+ class NodeBuilder < Ripper::SexpBuilder
+ def initialize(src, filename=nil, lineno=nil)
+ @src = src ||= filename && File.read(filename) || ''
+ @filename = filename
+ super
+ end
+
+ class << self
+ def build(src, filename=nil)
+ new(src, filename).parse
+ end
+ end
+
+ def on_alias(new_name, old_name)
+ Ruby::Alias.new(to_ident(new_name), to_ident(old_name))
+ end
+
+ def on_aref(target, args)
+ Ruby::Call.new(target, ident(:[]), args)
+ end
+
+ def on_aref_field(target, args)
+ Ruby::Call.new(target, ident(:[]), args)
+ end
+
+ def on_arg_paren(args)
+ args
+ end
+
+ def on_args_add(args, arg)
+ args.add(arg); args
+ end
+
+ def on_args_add_block(args, block)
+ args.add_block(block) if block; args
+ end
+
+ def on_args_new
+ Ruby::Args.new
+ end
+
+ def on_array(args)
+ args ? args.to_array : Ruby::Array.new
+ end
+
+ def on_assign(lvalue, rvalue)
+ lvalue.assignment(rvalue, ident(:'='))
+ end
+
+ def on_assoc_new(key, value)
+ Ruby::Assoc.new(key, value)
+ end
+
+ def on_assoclist_from_args(args)
+ args
+ end
+
+ def on_bare_assoc_hash(assocs)
+ Ruby::Hash.new(assocs)
+ end
+
+ def on_BEGIN(statements)
+ Ruby::Call.new(nil, ident(:BEGIN), nil, statements)
+ end
+
+ def on_begin(body)
+ body.is_a?(Ruby::ChainedBlock) ? body : body.to_chained_block
+ end
+
+ def on_binary(lvalue, operator, rvalue)
+ Ruby::Binary.new(lvalue, rvalue, operator)
+ end
+
+ def on_blockarg(arg)
+ arg
+ end
+
+ def on_block_var(params, something)
+ params
+ end
+
+ def on_bodystmt(body, rescue_block, else_block, ensure_block)
+ statements = [rescue_block, else_block, ensure_block].compact
+ statements.empty? ? body : body.to_chained_block(statements)
+ end
+
+ def on_brace_block(params, statements)
+ statements.to_block(params)
+ end
+
+ def on_break(args)
+ Ruby::Call.new(nil, ident(:break), args)
+ end
+
+ def on_call(target, separator, identifier)
+ Ruby::Call.new(target, identifier)
+ end
+
+ def on_case(args, when_block)
+ Ruby::Case.new(args, when_block)
+ end
+
+ def on_CHAR(token)
+ Ruby::Char.new(token, position)
+ end
+
+ def on_class(const, superclass, body)
+ Ruby::Class.new(const, superclass, body)
+ end
+
+ def on_class_name_error(ident)
+ raise SyntaxError, 'class/module name must be CONSTANT'
+ end
+
+ def on_command(identifier, args)
+ Ruby::Call.new(nil, identifier, args)
+ end
+
+ def on_command_call(target, separator, identifier, args)
+ Ruby::Call.new(target, identifier, args)
+ end
+
+ def on_const(token)
+ Ruby::Constant.new(token, position)
+ end
+
+ def on_const_path_field(namespace, const)
+ const.namespace = namespace; const
+ end
+
+ def on_const_path_ref(namespace, const)
+ const.namespace = namespace; const
+ end
+
+ def on_const_ref(const)
+ const
+ end
+
+ def on_cvar(token)
+ Ruby::ClassVariable.new(token, position)
+ end
+
+ def on_def(identifier, params, body)
+ Ruby::Method.new(nil, identifier, params, body)
+ end
+
+ def on_defs(target, separator, identifier, params, body)
+ Ruby::Method.new(target, identifier, params, body)
+ end
+
+ def on_defined(ref)
+ Ruby::Defined.new(ref)
+ end
+
+ def on_do_block(params, statements)
+ statements.to_block(params)
+ end
+
+ def on_dot2(min, max)
+ Ruby::Range.new(min, max, false)
+ end
+
+ def on_dot3(min, max)
+ Ruby::Range.new(min, max, true)
+ end
+
+ def on_dyna_symbol(symbol)
+ symbol.to_dyna_symbol
+ end
+
+ def on_else(statements)
+ Ruby::Else.new(statements)
+ end
+
+ def on_END(statements)
+ Ruby::Call.new(nil, ident(:END), nil, statements)
+ end
+
+ def on_ensure(statements)
+ statements
+ end
+
+ def on_if(expression, statements, else_block)
+ Ruby::If.new(expression, statements, else_block)
+ end
+ alias_method :on_elsif, :on_if
+
+ def on_ifop(condition, then_part, else_part)
+ Ruby::IfOp.new(condition, then_part, else_part)
+ end
+
+ def on_if_mod(expression, statement)
+ Ruby::IfMod.new(expression, statement)
+ end
+
+ def on_fcall(identifier)
+ Ruby::Call.new(nil, identifier)
+ end
+
+ def on_field(target, separator, identifier)
+ Ruby::Call.new(target, identifier)
+ end
+
+ def on_float(token)
+ Ruby::Float.new(token, position)
+ end
+
+ def on_for(variable, range, statements)
+ Ruby::For.new(variable, range, statements)
+ end
+
+ def on_gvar(token)
+ Ruby::GlobalVariable.new(token, position)
+ end
+
+ def on_hash(assocs)
+ Ruby::Hash.new(assocs)
+ end
+
+ def on_ident(token)
+ ident(token)
+ end
+
+ def on_int(token)
+ Ruby::Integer.new(token, position)
+ end
+
+ def on_ivar(token)
+ Ruby::InstanceVariable.new(token, position)
+ end
+
+ def on_kw(token)
+ Ruby::Keyword.new(token, position)
+ end
+
+ def on_label(token)
+ Ruby::Label.new(token, position)
+ end
+
+ def on_lambda(params, statements)
+ Ruby::Block.new(statements, params)
+ end
+
+ def on_massign(lvalue, rvalue)
+ lvalue.assignment(rvalue, ident(:'='))
+ end
+
+ def on_method_add_arg(call, args)
+ call.arguments = args; call
+ end
+
+ def on_method_add_block(call, block)
+ call.block = block; call
+ end
+
+ def on_mlhs_add(assignment, ref)
+ assignment.add(ref); assignment
+ end
+
+ def on_mlhs_add_star(assignment, ref)
+ assignment.add(Ruby::SplatArg.new(ref)); assignment
+ end
+
+ def on_mlhs_new
+ Ruby::MultiAssignmentList.new
+ end
+
+ def on_module(const, body)
+ Ruby::Module.new(const, body)
+ end
+
+ def on_mrhs_add(assignment, ref)
+ assignment.add(ref); assignment
+ end
+
+ def on_mrhs_new_from_args(args)
+ Ruby::MultiAssignmentList.new(args.elements)
+ end
+
+ def on_next(args)
+ Ruby::Call.new(nil, ident(:next), args)
+ end
+
+ def on_op(operator)
+ operator.intern
+ end
+
+ def on_opassign(lvalue, operator, rvalue)
+ lvalue.assignment(rvalue, operator)
+ end
+
+ def on_params(params, optionals, rest, something, block)
+ Ruby::Params.new(params, optionals, rest, block)
+ end
+
+ def on_paren(node)
+ node
+ end
+
+ def on_parse_error(message)
+ raise SyntaxError, message
+ end
+
+ def on_program(statements)
+ statements.to_program(@src, @filename)
+ end
+
+ def on_qwords_add(array, word)
+ array.add(Ruby::String.new(word)); array
+ end
+
+ def on_qwords_new
+ Ruby::Array.new
+ end
+
+ def on_redo
+ Ruby::Call.new(nil, ident(:redo))
+ end
+
+ def on_regexp_add(regexp, content)
+ regexp.add(content); regexp
+ end
+
+ def on_regexp_literal(regexp, rdelim)
+ regexp
+ end
+
+ def on_regexp_new
+ Ruby::Regexp.new
+ end
+
+ def on_rescue(types, var, statements, block)
+ statements.to_chained_block(block, Ruby::RescueParams.new(types, var))
+ end
+
+ def on_rescue_mod(expression, statements)
+ Ruby::RescueMod.new(expression, statements)
+ end
+
+ def on_rest_param(param)
+ param
+ end
+
+ def on_retry
+ Ruby::Call.new(nil, ident(:retry))
+ end
+
+ def on_return(args)
+ Ruby::Call.new(nil, ident(:return), args)
+ end
+
+ def on_sclass(superclass, body)
+ Ruby::SingletonClass.new(superclass, body)
+ end
+
+ def on_stmts_add(target, statement)
+ target.add(statement) if statement; target
+ end
+
+ def on_stmts_new
+ Ruby::Statements.new
+ end
+
+ def on_string_add(string, content)
+ string.add(content); string
+ end
+
+ def on_string_concat(*strings)
+ Ruby::StringConcat.new(strings)
+ end
+
+ def on_string_content
+ Ruby::String.new
+ end
+
+ # weird string syntax that I didn't know existed until writing this lib.
+ # ex. "safe level is #$SAFE" => "safe level is 0"
+ def on_string_dvar(variable)
+ variable
+ end
+
+ def on_string_embexpr(expression)
+ expression
+ end
+
+ def on_string_literal(string)
+ string
+ end
+
+ def on_super(args)
+ Ruby::Call.new(nil, ident(:super), args)
+ end
+
+ def on_symbol(token)
+ Ruby::Symbol.new(token, position)
+ end
+
+ def on_symbol_literal(symbol)
+ symbol
+ end
+
+ def on_top_const_field(field)
+ field
+ end
+
+ def on_top_const_ref(const)
+ const
+ end
+
+ def on_tstring_content(token)
+ token
+ end
+
+ def on_unary(operator, operand)
+ Ruby::Unary.new(operator, operand)
+ end
+
+ def on_undef(args)
+ Ruby::Call.new(nil, ident(:undef), Ruby::Args.new(args.collect { |e| to_ident(e) }))
+ end
+
+ def on_unless(expression, statements, else_block)
+ Ruby::Unless.new(expression, statements, else_block)
+ end
+
+ def on_unless_mod(expression, statement)
+ Ruby::UnlessMod.new(expression, statement)
+ end
+
+ def on_until(expression, statements)
+ Ruby::Until.new(expression, statements)
+ end
+
+ def on_until_mod(expression, statement)
+ Ruby::UntilMod.new(expression, statement)
+ end
+
+ def on_var_alias(new_name, old_name)
+ Ruby::Alias.new(to_ident(new_name), to_ident(old_name))
+ end
+
+ def on_var_field(field)
+ field
+ end
+
+ def on_var_ref(ref)
+ ref
+ end
+
+ def on_void_stmt
+ nil
+ end
+
+ def on_when(expression, statements, next_block)
+ Ruby::When.new(expression, statements, next_block)
+ end
+
+ def on_while(expression, statements)
+ Ruby::While.new(expression, statements)
+ end
+
+ def on_while_mod(expression, statement)
+ Ruby::WhileMod.new(expression, statement)
+ end
+
+ def on_word_add(string, word)
+ string.add(word); string
+ end
+
+ def on_words_add(array, word)
+ array.add(word); array
+ end
+
+ def on_word_new
+ Ruby::String.new
+ end
+
+ def on_words_new
+ Ruby::Array.new
+ end
+
+ def on_xstring_add(string, content)
+ on_string_add(string, content)
+ end
+
+ def on_xstring_new
+ Ruby::ExecutableString.new
+ end
+
+ def on_xstring_literal(string)
+ string
+ end
+
+ def on_yield(args)
+ Ruby::Call.new(nil, ident(:yield), args)
+ end
+
+ def on_yield0
+ Ruby::Call.new(nil, ident(:yield))
+ end
+
+ def on_zsuper(*)
+ Ruby::Call.new(nil, ident(:super))
+ end
+
+ private
+
+ def ident(ident)
+ Ruby::Identifier.new(ident, position)
+ end
+
+ def to_ident(ident_or_sym)
+ ident_or_sym.is_a?(Ruby::Identifier) ? ident_or_sym : ident(ident_or_sym)
+ end
+
+ def position
+ Ruby::Position.new(lineno, column)
+ end
+ end
+end
352 lib/ruby_cop/policy.rb
@@ -0,0 +1,352 @@
+require 'set'
+
+module RubyCop
+ class Policy
+ def initialize
+ @const_list = GrayList.new
+ initialize_const_blacklist
+ end
+
+ def inspect
+ '#<%s:0x%x>' % [self.class.name, object_id]
+ end
+
+ def blacklist_const(const)
+ @const_list.blacklist(const)
+ end
+
+ def const_allowed?(const)
+ @const_list.allow?(const)
+ end
+
+ def whitelist_const(const)
+ @const_list.whitelist(const)
+ end
+
+ def visit(node)
+ klass = node.class.ancestors.detect do |ancestor|
+ respond_to?("visit_#{ancestor.name.split('::').last}")
+ end
+ if klass
+ send("visit_#{klass.name.split('::').last}", node)
+ else
+ warn "unhandled node type: #{node.inspect}:#{node.class.name}"
+ true
+ end
+ end
+
+ def visit_Alias(node)
+ false # never allowed
+ end
+
+ def visit_Args(node)
+ node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_Array(node)
+ node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_Assoc(node)
+ visit(node.key) && visit(node.value)
+ end
+
+ def visit_Binary(node)
+ visit(node.lvalue) && visit(node.rvalue)
+ end
+
+ def visit_Block(node)
+ (node.params.nil? || visit(node.params)) && node.elements.all? { |e| visit(e) }
+ end
+
+ CALL_BLACKLIST = %w[
+ abort
+ alias_method
+ at_exit
+ autoload
+ binding
+ callcc
+ caller
+ class_eval
+ const_get
+ const_set
+ eval
+ exec
+ exit
+ fail
+ fork
+ gets
+ global_variables
+ instance_eval
+ load
+ loop
+ method
+ module_eval
+ open
+ readline
+ readlines
+ redo
+ remove_const
+ require
+ send
+ set_trace_func
+ sleep
+ spawn
+ srand
+ syscall
+ system
+ trap
+ undef
+ __callee__
+ __method__
+ ].to_set.freeze
+
+ def visit_Call(node)
+ !CALL_BLACKLIST.include?(node.identifier.token.to_s) && [node.target, node.arguments, node.block].compact.all? { |e| visit(e) }
+ end
+
+ def visit_Case(node)
+ visit(node.expression) && visit(node.block)
+ end
+
+ def visit_ChainedBlock(node)
+ node.elements.all? { |e| visit(e) } && node.blocks.all? { |e| visit(e) } && (node.params.nil? || visit(node.params))
+ end
+
+ def visit_Class(node)
+ visit(node.const) && (node.superclass.nil? || visit(node.superclass)) && visit(node.body)
+ end
+
+ def visit_ClassVariable(node)
+ false # never allowed
+ end
+
+ def visit_ClassVariableAssignment(node)
+ false # never allowed
+ end
+
+ def visit_Char(node)
+ true
+ end
+
+ def visit_Constant(node)
+ const_allowed?(node.token)
+ end
+
+ def visit_ConstantAssignment(node)
+ visit(node.lvalue) && visit(node.rvalue)
+ end
+
+ def visit_Defined(node)
+ false # never allowed (though it's probably safe)
+ end
+
+ def visit_Else(node)
+ node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_ExecutableString(node)
+ false # never allowed
+ end
+
+ def visit_Float(node)
+ true
+ end
+
+ def visit_For(node)
+ visit(node.variable) && visit(node.range) && visit(node.statements)
+ end
+
+ def visit_GlobalVariable(node)
+ false # never allowed
+ end
+
+ def visit_GlobalVariableAssignment(node)
+ false # never allowed
+ end
+
+ def visit_Hash(node)
+ node.assocs.nil? || node.assocs.all? { |e| visit(e) }
+ end
+
+ def visit_Identifier(node)
+ !CALL_BLACKLIST.include?(node.token)
+ end
+
+ def visit_If(node)
+ visit(node.expression) && node.elements.all? { |e| visit(e) } && node.blocks.all? { |e| visit(e) }
+ end
+ alias_method :visit_Unless, :visit_If
+
+ def visit_IfMod(node)
+ visit(node.expression) && node.elements.all? { |e| visit(e) }
+ end
+ alias_method :visit_UnlessMod, :visit_IfMod
+
+ def visit_IfOp(node)
+ visit(node.condition) && visit(node.then_part) && visit(node.else_part)
+ end
+
+ def visit_InstanceVariable(node)
+ true
+ end
+
+ def visit_InstanceVariableAssignment(node)
+ visit(node.rvalue)
+ end
+
+ def visit_Integer(node)
+ true
+ end
+
+ KEYWORD_WHITELIST = %w[
+ false
+ nil
+ self
+ true
+ ].to_set.freeze
+
+ def visit_Keyword(node)
+ KEYWORD_WHITELIST.include?(node.token)
+ end
+
+ def visit_Label(node)
+ true
+ end
+
+ def visit_LocalVariableAssignment(node)
+ visit(node.rvalue)
+ end
+
+ def visit_Method(node)
+ [node.target, node.params, node.body].compact.all? { |e| visit(e) }
+ end
+
+ def visit_Module(node)
+ visit(node.const) && visit(node.body)
+ end
+
+ def visit_MultiAssignment(node)
+ visit(node.lvalue) && visit(node.rvalue)
+ end
+
+ def visit_MultiAssignmentList(node)
+ node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_Params(node)
+ node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_Program(node)
+ node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_Range(node)
+ visit(node.min) && visit(node.max)
+ end
+
+ def visit_RescueMod(node)
+ node.elements.all? { |e| visit(e) } && visit(node.expression)
+ end
+
+ def visit_RescueParams(node)
+ node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_SingletonClass(node)
+ visit(node.superclass) && visit(node.body)
+ end
+
+ def visit_SplatArg(node)
+ visit(node.arg)
+ end
+
+ def visit_Statements(node)
+ node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_String(node)
+ # embedded strings can have statements in them, so check those
+ node.elements.reject { |e| e.is_a?(::String) }.all? { |e| visit(e) }
+ end
+
+ def visit_StringConcat(node)
+ node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_Symbol(node)
+ true
+ end
+
+ def visit_Unary(node)
+ visit(node.operand)
+ end
+
+ def visit_Until(node)
+ false # never allowed
+ end
+ alias_method :visit_UntilMod, :visit_Until
+
+ def visit_When(node)
+ visit(node.expression) && node.elements.all? { |e| visit(e) }
+ end
+
+ def visit_While(node)
+ false # never allowed
+ end
+ alias_method :visit_WhileMod, :visit_While
+
+ private
+
+ CONST_BLACKLIST = %w[
+ ARGF
+ ARGV
+ Array
+ Base64
+ Class
+ Dir
+ ENV
+ Enumerable
+ Error
+ Exception
+ Fiber
+ File
+ FileUtils
+ GC
+ Gem
+ Hash
+ IO
+ IRB
+ Kernel
+ Module
+ Net
+ Object
+ ObjectSpace
+ OpenSSL
+ OpenURI
+ PLATFORM
+ Proc
+ Process
+ RUBY_COPYRIGHT
+ RUBY_DESCRIPTION
+ RUBY_ENGINE
+ RUBY_PATCHLEVEL
+ RUBY_PLATFORM
+ RUBY_RELEASE_DATE
+ RUBY_VERSION
+ Rails
+ STDERR
+ STDIN
+ STDOUT
+ String
+ TOPLEVEL_BINDING
+ Thread
+ VERSION
+ ].freeze
+
+ def initialize_const_blacklist
+ CONST_BLACKLIST.each { |const| blacklist_const(const) }
+ end
+ end
+end
23 lib/ruby_cop/ruby.rb
@@ -0,0 +1,23 @@
+require 'ruby_cop/ruby/node'
+require 'ruby_cop/ruby/list'
+require 'ruby_cop/ruby/array'
+require 'ruby_cop/ruby/args'
+require 'ruby_cop/ruby/assignment'
+require 'ruby_cop/ruby/assoc'
+require 'ruby_cop/ruby/statements'
+require 'ruby_cop/ruby/blocks'
+require 'ruby_cop/ruby/call'
+require 'ruby_cop/ruby/case'
+require 'ruby_cop/ruby/tokens'
+require 'ruby_cop/ruby/constants'
+require 'ruby_cop/ruby/definitions'
+require 'ruby_cop/ruby/for'
+require 'ruby_cop/ruby/hash'
+require 'ruby_cop/ruby/if'
+require 'ruby_cop/ruby/operators'
+require 'ruby_cop/ruby/params'
+require 'ruby_cop/ruby/position'
+require 'ruby_cop/ruby/range'
+require 'ruby_cop/ruby/string'
+require 'ruby_cop/ruby/variables'
+require 'ruby_cop/ruby/while'
26 lib/ruby_cop/ruby/args.rb
@@ -0,0 +1,26 @@
+module RubyCop
+ module Ruby
+ class Args < List
+ attr_reader :block
+
+ def add_block(block)
+ @block = block
+ end
+
+ def to_array
+ Array.new(@elements)
+ end
+ end
+
+ class Arg < Node
+ def initialize(arg)
+ @arg = arg
+ end
+
+ attr_reader :arg
+ end
+
+ class SplatArg < Arg
+ end
+ end
+end
9 lib/ruby_cop/ruby/array.rb
@@ -0,0 +1,9 @@
+module RubyCop
+ module Ruby
+ class Array < List
+ # def inspect
+ # '[%s]' % @elements.collect { |e| e.inspect }.join(', ')
+ # end
+ end
+ end
+end
43 lib/ruby_cop/ruby/assignment.rb
@@ -0,0 +1,43 @@
+module RubyCop
+ module Ruby
+ class Assignment < Node
+ def initialize(lvalue, rvalue, operator)
+ @lvalue = lvalue
+ @rvalue = rvalue
+ @operator = operator
+ end
+
+ attr_reader :lvalue
+ attr_reader :rvalue
+ attr_reader :operator
+
+ # def inspect
+ # "#{@lvalue.inspect} #{@operator} #{@rvalue.inspect}"
+ # end
+ end
+
+ class ClassVariableAssignment < Assignment
+ end
+
+ class ConstantAssignment < Assignment
+ end
+
+ class GlobalVariableAssignment < Assignment
+ end
+
+ class InstanceVariableAssignment < Assignment
+ end
+
+ class LocalVariableAssignment < Assignment
+ end
+
+ class MultiAssignment < Assignment
+ end
+
+ class MultiAssignmentList < List
+ def assignment(rvalue, operator)
+ MultiAssignment.new(self, rvalue, operator)
+ end
+ end
+ end
+end
13 lib/ruby_cop/ruby/assoc.rb
@@ -0,0 +1,13 @@
+module RubyCop
+ module Ruby
+ class Assoc < Node
+ def initialize(key, value)
+ @key = key
+ @value = value
+ end
+
+ attr_reader :key
+ attr_reader :value
+ end
+ end
+end
21 lib/ruby_cop/ruby/blocks.rb
@@ -0,0 +1,21 @@
+module RubyCop
+ module Ruby
+ class Block < Statements
+ def initialize(statements, params=nil)
+ @params = params
+ super(statements)
+ end
+
+ attr_reader :params
+ end
+
+ class ChainedBlock < Block
+ def initialize(blocks, statements, params=nil)
+ @blocks = Array(blocks).compact
+ super(statements, params)
+ end
+
+ attr_reader :blocks
+ end
+ end
+end
31 lib/ruby_cop/ruby/call.rb
@@ -0,0 +1,31 @@
+module RubyCop
+ module Ruby
+ class Call < Node
+ def initialize(target, identifier, arguments=nil, block=nil)
+ @target = target
+ @identifier = identifier
+ @arguments = arguments
+ @block = block
+ end
+
+ attr_reader :target
+ attr_reader :identifier
+ attr_accessor :arguments
+ attr_accessor :block
+
+ def assignment(rvalue, operator)
+ self.class.new(@target, Identifier.new("#{@identifier.token}=", @identifier.position), @arguments, @block)
+ end
+ end
+
+ class Alias < Node
+ def initialize(new_name, old_name)
+ @new_name = new_name
+ @old_name = old_name
+ end
+
+ attr_reader :new_name
+ attr_reader :old_name
+ end
+ end
+end
22 lib/ruby_cop/ruby/case.rb
@@ -0,0 +1,22 @@
+module RubyCop
+ module Ruby
+ class Case < Node
+ def initialize(expression, block)
+ @expression = expression
+ @block = block
+ end
+
+ attr_reader :expression
+ attr_reader :block
+ end
+
+ class When < ChainedBlock
+ def initialize(expression, statements, block=nil)
+ @expression = expression
+ super([block], statements)
+ end
+
+ attr_reader :expression
+ end
+ end
+end
47 lib/ruby_cop/ruby/constants.rb
@@ -0,0 +1,47 @@
+module RubyCop
+ module Ruby
+ class Constant < Identifier
+ attr_accessor :namespace
+
+ def assignment(rvalue, operator)
+ ConstantAssignment.new(self, rvalue, operator)
+ end
+
+ # def inspect
+ # @namespace ? "#{@namespace.inspect}::#{@token}" : @token
+ # end
+ end
+
+ class Module < Node
+ def initialize(const, body)
+ @const = const
+ @body = body
+ end
+
+ attr_reader :const
+ attr_reader :body
+ end
+
+ class Class < Node
+ def initialize(const, superclass, body)
+ @const = const
+ @superclass = superclass
+ @body = body
+ end
+
+ attr_reader :const
+ attr_reader :superclass
+ attr_reader :body
+ end
+
+ class SingletonClass < Node
+ def initialize(superclass, body)
+ @superclass = superclass
+ @body = body
+ end
+
+ attr_reader :superclass
+ attr_reader :body
+ end
+ end
+end
25 lib/ruby_cop/ruby/definitions.rb
@@ -0,0 +1,25 @@
+module RubyCop
+ module Ruby
+ class Method < Node
+ def initialize(target, identifier, params, body)
+ @target = target
+ @identifier = identifier
+ @params = params
+ @body = body
+ end
+
+ attr_reader :target
+ attr_reader :identifier
+ attr_reader :params
+ attr_reader :body
+ end
+
+ class Defined < Node
+ def initialize(expression)
+ @expression = expression
+ end
+
+ attr_reader :expression
+ end
+ end
+end
15 lib/ruby_cop/ruby/for.rb
@@ -0,0 +1,15 @@
+module RubyCop
+ module Ruby
+ class For < Block
+ def initialize(variable, range, statements)
+ @variable = variable
+ @range = range
+ @statements = statements
+ end
+
+ attr_reader :variable
+ attr_reader :range
+ attr_reader :statements
+ end
+ end
+end
11 lib/ruby_cop/ruby/hash.rb
@@ -0,0 +1,11 @@
+module RubyCop
+ module Ruby
+ class Hash < Node
+ def initialize(assocs)
+ @assocs = assocs
+ end
+
+ attr_reader :assocs
+ end
+ end
+end
31 lib/ruby_cop/ruby/if.rb
@@ -0,0 +1,31 @@
+module RubyCop
+ module Ruby
+ class If < ChainedBlock
+ def initialize(expression, statements=nil, else_block=nil)
+ @expression = expression
+ super([else_block], statements, nil)
+ end
+ attr_reader :expression
+ end
+
+ class Unless < If
+ end
+
+ class Else < Block
+ end
+
+ class IfMod < Block
+ def initialize(expression, statements)
+ @expression = expression
+ super(statements)
+ end
+ attr_reader :expression
+ end
+
+ class UnlessMod < IfMod
+ end
+
+ class RescueMod < IfMod
+ end
+ end
+end
15 lib/ruby_cop/ruby/list.rb
@@ -0,0 +1,15 @@
+module RubyCop
+ module Ruby
+ class List < Node
+ def initialize(elements=nil)
+ @elements = Array(elements).compact # TODO: compact might cause problems here
+ end
+
+ attr_reader :elements
+
+ def add(element)
+ @elements.push(element)
+ end
+ end
+ end
+end
9 lib/ruby_cop/ruby/node.rb
@@ -0,0 +1,9 @@
+module RubyCop
+ module Ruby
+ class Node
+ def accept(visitor)
+ visitor.visit(self)
+ end
+ end
+ end
+end
52 lib/ruby_cop/ruby/operators.rb
@@ -0,0 +1,52 @@
+module RubyCop
+ module Ruby
+ class Operator < Node
+ end
+
+ class Unary < Operator
+ def initialize(operator, operand)
+ @operator = operator
+ @operand = operand
+ end
+
+ attr_reader :operator
+ attr_reader :operand
+
+ # def inspect
+ # "#{@operator}(#{@operand.inspect})"
+ # end
+ end
+
+ class Binary < Operator
+ def initialize(lvalue, rvalue, operator)
+ @lvalue = lvalue
+ @rvalue = rvalue
+ @operator = operator
+ end
+
+ attr_reader :lvalue
+ attr_reader :rvalue
+ attr_reader :operator
+
+ # def inspect
+ # "#{@lvalue.inspect} #{@operator} #{@rvalue.inspect}"
+ # end
+ end
+
+ class IfOp < Operator
+ def initialize(condition, then_part, else_part)
+ @condition = condition
+ @then_part = then_part
+ @else_part = else_part
+ end
+
+ attr_reader :condition
+ attr_reader :then_part
+ attr_reader :else_part
+
+ # def inspect
+ # "#{@condition.inspect} ? #{@then_part.inspect} : #{@else_part.inspect}"
+ # end
+ end
+ end
+end
21 lib/ruby_cop/ruby/params.rb
@@ -0,0 +1,21 @@
+module RubyCop
+ module Ruby
+ class Params < List
+ def initialize(params, optionals, rest, block)
+ super((Array(params) + Array(optionals) << rest << block).flatten.compact)
+ end
+ end
+
+ class RescueParams < List
+ def initialize(types, var)
+ if types
+ errors = Ruby::Array.new(types)
+ errors = Ruby::Assoc.new(errors, var) if var
+ super(errors)
+ else
+ super()
+ end
+ end
+ end
+ end
+end
13 lib/ruby_cop/ruby/position.rb
@@ -0,0 +1,13 @@
+module RubyCop
+ module Ruby
+ class Position
+ def initialize(lineno, column)
+ @lineno = lineno
+ @column = column
+ end
+
+ attr_reader :lineno
+ attr_reader :column
+ end
+ end
+end
15 lib/ruby_cop/ruby/range.rb
@@ -0,0 +1,15 @@
+module RubyCop
+ module Ruby
+ class Range < Node
+ def initialize(min, max, exclude_end)
+ @min = min
+ @max = max
+ @exclude_end = exclude_end
+ end
+
+ attr_reader :min
+ attr_reader :max
+ attr_reader :exclude_end
+ end
+ end
+end
32 lib/ruby_cop/ruby/statements.rb
@@ -0,0 +1,32 @@
+module RubyCop
+ module Ruby
+ class Statements < List
+ # def inspect
+ # @elements.collect { |e| e.inspect }.join
+ # end
+
+ def to_block(params)
+ Block.new(@elements, params)
+ end
+
+ def to_chained_block(blocks=nil, params=nil)
+ ChainedBlock.new(blocks, @elements, params)
+ end
+
+ def to_program(src, filename)
+ Program.new(src, filename, @elements)
+ end
+ end
+
+ class Program < Statements
+ def initialize(src, filename, statements)
+ @src = src
+ @filename = filename
+ super(statements)
+ end
+
+ attr_reader :src
+ attr_reader :filename
+ end
+ end
+end
24 lib/ruby_cop/ruby/string.rb
@@ -0,0 +1,24 @@
+module RubyCop
+ module Ruby
+ class StringConcat < List
+ end
+
+ class String < List
+ # def inspect
+ # @elements.join.inspect
+ # end
+ end
+
+ class DynaSymbol < String
+ end
+
+ class ExecutableString < String
+ def to_dyna_symbol
+ DynaSymbol.new(@elements)
+ end
+ end
+
+ class Regexp < String
+ end
+ end
+end
44 lib/ruby_cop/ruby/tokens.rb
@@ -0,0 +1,44 @@
+module RubyCop
+ module Ruby
+ class Token < Node
+ def initialize(token, position)
+ @token = token
+ @position = position
+ end
+
+ attr_reader :token
+ attr_reader :position
+
+ # def inspect
+ # "#{@token}<t>"
+ # end
+ end
+
+ class Integer < Token
+ end
+
+ class Float < Token
+ end
+
+ class Char < Token
+ end
+
+ class Label < Token
+ end
+
+ class Symbol < Token
+ # def inspect
+ # ":#{@token.inspect}"
+ # end
+ end
+
+ class Keyword < Token
+ end
+
+ class Identifier < Token
+ def assignment(rvalue, operator)
+ LocalVariableAssignment.new(self, rvalue, operator)
+ end
+ end
+ end
+end
24 lib/ruby_cop/ruby/variables.rb
@@ -0,0 +1,24 @@
+module RubyCop
+ module Ruby
+ class Variable < Identifier
+ end
+
+ class ClassVariable < Variable
+ def assignment(rvalue, operator)
+ ClassVariableAssignment.new(self, rvalue, operator)
+ end
+ end
+
+ class GlobalVariable < Variable
+ def assignment(rvalue, operator)
+ GlobalVariableAssignment.new(self, rvalue, operator)
+ end
+ end
+
+ class InstanceVariable < Variable
+ def assignment(rvalue, operator)
+ InstanceVariableAssignment.new(self, rvalue, operator)
+ end
+ end
+ end
+end
3 lib/ruby_cop/ruby/version.rb
@@ -0,0 +1,3 @@
+module RubyCop
+ VERSION = "1.0.0"
+end
27 lib/ruby_cop/ruby/while.rb
@@ -0,0 +1,27 @@
+module RubyCop
+ module Ruby
+ class While < Block
+ def initialize(expression, statements)
+ @expression = expression
+ super(statements)
+ end
+
+ attr_reader :expression
+ end
+
+ class WhileMod < Block
+ def initialize(expression, statements)
+ @expression = expression
+ super(statements)
+ end
+
+ attr_reader :expression
+ end
+
+ class Until < While
+ end
+
+ class UntilMod < WhileMod
+ end
+ end
+end
3 lib/ruby_cop/version.rb
@@ -0,0 +1,3 @@
+module RubyCop
+ VERSION = '1.0.0'
+end
24 ruby_cop.gemspec
@@ -0,0 +1,24 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "ruby_cop/version"
+
+Gem::Specification.new do |s|
+ s.name = "ruby_cop"
+ s.version = RubyCop::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = ["Dray Lacy", "Eric Allam"]
+ s.email = ["dray@envylabs.com", "eric@envylabs.com"]
+ s.homepage = ""
+ s.summary = %q{Statically analyze Ruby and polic any nefarious code}
+ s.description = %q{Statically analyze Ruby and polic any nefarious code}
+
+ s.rubyforge_project = "ruby_cop"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.add_development_dependency 'rspec', '~> 2.3.0'
+ s.add_development_dependency 'geminabox'
+end
374 spec/analyzer/node_builder_spec.rb
@@ -0,0 +1,374 @@
+require 'spec_helper'
+
+describe RubyCop::NodeBuilder do
+ subject { described_class }
+
+ RSpec::Matchers.define(:parse) do |ruby|
+ match { |nb| nb.build(ruby).is_a?(RubyCop::Ruby::Node) }
+ end
+
+ context "arrays" do
+ it { should parse('[:foo, :bar]') }
+ it { should parse('%w(foo bar)') }
+ it { should parse('%(foo)') }
+ it { should parse("%(\nfoo bar\n)") }
+ it { should parse("%( \nfoo bar\n )") }
+ it { should parse('%W[foo bar]') }
+ it { should parse('%w()') }
+ it { should parse('%W()') }
+ it { should parse('%w[]') }
+ it { should parse('%W[]') }
+ it { should parse('%W[#{foo}]') }
+ it { should parse('foo[1]') }
+ it { should parse('foo[]') }
+ it { should parse('foo.bar[]') }
+ it { should parse('[ foo[bar] ]') }
+ it { should parse('result[0] = :value') }
+ it { should parse("\n [type, [row]]\n") }
+ end
+
+ context "assignment" do
+ it { should parse('a = b') }
+ it { should parse('a ||= b') }
+ it { should parse('a, b = c') }
+ it { should parse('a, b = c, d') }
+ it { should parse('a, *b = c') }
+ it { should parse('A::B = 1') }
+ end
+
+ context "blocks" do
+ it { should parse("t do\nfoo\nbar\nend") }
+ it { should parse('t do ; end') }
+ it { should parse("t do ||\nfoo\nend") }
+ it { should parse('t do ;; ;foo ; ;;bar; ; end') }
+ it { should parse("t do |(a, b), *c|\n foo\n bar\nend") }
+ it { should parse('t { |(a, b), *c| }') }
+ it { should parse('t do |(a, b), *c| end') }
+ it { should parse('t do |*a, &c| end') }
+ it { should parse('t { |a, (b)| }') }
+ it { should parse("begin\nend") }
+ it { should parse('begin ; end') }
+ it { should parse('begin foo; end') }
+ it { should parse("begin\nfoo\nelse\nbar\nend") }
+ it { should parse("begin\nfoo\nrescue\nbar\nend") }
+ it { should parse("begin \n rescue A \n end") }
+ it { should parse("begin foo\n rescue A => e\n bar\n end") }
+ it { should parse("begin foo\n rescue A, B => e\n bar\n end") }
+ it { should parse("begin foo\n rescue A, B => e\n bar\nrescue C => e\n bam\nensure\nbaz\n end") }
+ it { should parse('begin a; rescue NameError => e then e else :foo end') }
+ it { should parse("begin\nrescue A => e\nrescue B\nend") }
+ it { should parse("begin\nrescue A\nelse\nend\n") }
+ it { should parse('foo rescue bar') }
+ it { should parse('foo rescue 0') }
+ end
+
+ context "calls" do
+ it { should parse('t') }
+ it { should parse('I18n.t') }
+ it { should parse('a.b(:foo)') }
+ it { should parse('I18n.t()') }
+ it { should parse("I18n.t('foo')") }
+ it { should parse("I18n.t 'foo'") }
+ it { should parse('I18n::t') }
+ it { should parse('I18n::t()') }
+ it { should parse("I18n::t('foo')") }
+ it { should parse('foo.<=>(bar)') }
+ it { should parse('foo.<< bar') }
+ it { should parse('foo("#{bar}")') }
+ it { should parse("t('foo', 'bar')") }
+ it { should parse("t('foo', 'bar'); t 'baz'") }
+ it { should parse("t 'foo'") }
+ it { should parse('t %(foo #{bar}), %(baz)') }
+ it { should parse('t()') }
+ it { should parse('t(:a, b(:b))') }
+ it { should parse('t(:`)') }
+ it { should parse("t do |a, b, *c|\nfoo\nend") }
+ it { should parse("t do |(a, b), *c|\nfoo\nend") }
+ it { should parse('t(:foo, &block)') }
+ it { should parse('foo(bar do |a| ; end)') }
+ it { should parse('self.b') }
+ it { should parse('super') }
+ it { should parse('super(:foo)') }
+ it { should parse('yield') }
+ it { should parse('yield(:foo)') }
+ it { should parse('return :foo') }
+ it { should parse('next') }
+ it { should parse('redo') }
+ it { should parse('break') }
+ it { should parse('retry') }
+ it { should parse('a.b = :c') }
+ it { should parse('defined?(A)') }
+ it { should parse('BEGIN { foo }') }
+ it { should parse('END { foo }') }
+ it { should parse('alias eql? ==') }
+ it { should parse('alias :foo :bar') }
+ it { should parse('alias $ERROR_INFO $!') }
+ it { should parse('undef :foo') }
+ it { should parse('undef foo, bar, baz') }
+ it { should parse('undef =~') }
+ end
+
+ context "case" do
+ it { should parse('case a; when 1; 2 end') }
+ it { should parse('case (a) when 1; 2 end') }
+ it { should parse('case (a;) when 1; 2 end') }
+ it { should parse('case (a); when 1; 2 end') }
+ it { should parse('case (a;;); when 1; 2 end') }
+ it { should parse('case (a;b); when 1; 2 end') }
+ it { should parse('case a; when 1; 2 end') }
+ it { should parse('case a; when 1, 2; 2 end') }
+ it { should parse('case a; when (1; 2), (3; 4;); 2 end') }
+ it { should parse('case (true); when 1; false; when 2, 3, 4; nil; else; nil; end') }
+ it { should parse("case true\n when 1\n false\n when 2\n nil\n else\n nil\n end") }
+ it { should parse("case true\n when 1 then false\n when 2 then nil\n else\n nil\n end") }
+ end
+
+ context "constants" do
+ it { should parse('A') }
+ it { should parse('module A::B ; end') }
+ it { should parse('class A::B < C ; end') }
+ it { should parse('self.class::A') }
+ it { should parse('class << self; self; end') }
+ it { should parse('foo (class << @bar; self; end)') }
+ it { should parse("class A ; end\n::B = 3") }
+ end
+
+ context "for" do
+ it { should parse('for i in [1]; a; end') }
+ it { should parse("for i in [1]\n a\n end") }
+ it { should parse("for i in [1] do a\n end") }
+ it { should parse("lambda do\n for a in b\n end\nend") }
+ it { should parse("\nfor i in 0...1 do\n for j in 0...2 do\n end\nend\n") }
+ end
+
+ context "hash" do
+ it { should parse('{ :foo => :bar }') }
+ it { should parse('{ :a => { :b => { :b => :c, }, }, }') }
+ it { should parse('t(:a => { :b => :b, :c => { :d => :d } })') }
+ it { should parse('{ :if => :foo }') }
+ it { should parse('{ :a => :a, :b => "#{b 1}" }') }
+ it { should parse('{ foo: bar }') }
+ it { should parse('t(:a => :a, :b => :b)') }
+ it { should parse('foo[:bar] = :baz') }
+ end
+
+ context "heredoc" do
+ it { should parse("\n<<-eos\neos\n") }
+ it { should parse("<<-eos\nfoo\neos") }
+ it { should parse("'string'\n<<-eos\nheredoc\neos\n'string'") }
+ it { should parse(":'symbol'\n<<-eos\nheredoc\neos\n:'symbol'") }
+ it { should parse("%w(words)\n<<-eoc\n heredoc\neoc\n%w(words)") }
+ it { should parse("foo(%w(words))\n<<-eoc\n\neoc") }
+ it { should parse("\n<<-end;\nfoo\nend\n") }
+ it { should parse("\n<<-'end' ;\n foo\nend\nfoo;") }
+ it { should parse("<<-eos\n foo \#{bar} baz\neos") }
+ it { should parse("<<-end\n\#{a['b']}\nend") }
+ it { should parse("<<-end\n\#{'a'}\nend") }
+ it { should parse("foo(<<-eos)\n foo\neos") }
+ it { should parse("foo(<<-eos\n foo\neos\n)") }
+ it { should parse("foo(<<-eos, __FILE__, __LINE__ + 1)\n foo\neos") }
+ it { should parse("foo(<<-eos, __FILE__, line)\n foo\neos") }
+ it { should parse("foo(<<-eos, \"\#{bar}\")\n foo\neos") }
+ it { should parse("begin <<-src \n\n\nfoo\nsrc\n end") }
+ it { should parse("each do\n <<-src \n\nfoo\n\#{bar}\nsrc\n end\n") }
+ it { should parse("<<-eos\n \#{\"\n \#{sym}\n \"}\neos") }
+ it { should parse("<<-eos\n \t\#{ a\n }\neos") }
+ it { should parse("<<-eos\n\#{:'dyna_symbol'}\neos") }
+ it { should parse("<<-eos # comment\neos\nfoo\nfoo\n") }
+ it { should parse("<<-eos # comment\nstring\neos\n'index'") }
+ it { should parse("foo <<-eos if bar\na\neos\nbaz") }
+ it { should parse("<<-eos.foo\neos\nbar\n") }
+ it { should parse("<<-end\nend\n<<-end\n\nend\n") }
+ it { should parse("heredocs = <<-\"foo\", <<-\"bar\"\n I said foo.\nfoo\n I said bar.\nbar\n") }
+ it { should parse("a = %w[]\n<<-eos\nfoo\neos") }
+ it { should parse("<<-eos\nfoo\n__END__\neos") }
+ it { should parse("foo = <<-`foo`\nfoo") }
+ end
+
+ context "identifier" do
+ it { should parse('foo') }
+ it { should parse('@foo') }
+ it { should parse('@@foo') }
+ it { should parse('$foo') }
+ it { should parse('__FILE__') }
+ it { should parse('__LINE__') }
+ it { should parse('__ENCODING__') }
+ end
+
+ context "if" do
+ it { should parse('if true; false end') }
+ it { should parse("if true\n false\n end") }
+ it { should parse("if true\n false\n end") }
+ it { should parse('if true then false end') }
+ it { should parse('if true; false; else; true end') }
+ it { should parse("if true\n false\n else\n true end") }
+ it { should parse("if true\n false\n elsif false\n true end") }
+ it { should parse("if true then false; elsif false then true; else nil end") }
+ it { should parse("if a == 1 then b\nelsif b == c then d\ne\nf\nelse e end") }
+ it { should parse('foo if true') }
+ it { should parse('return if true') }
+ it { should parse('foo.bar += bar if bar') }
+ it { should parse('foo, bar = *baz if bum') }
+ it { should parse('foo *args if bar?') }
+ it { should parse('pos[1] if pos') }
+ it { should parse('a if (defined? a)') }
+ it { should parse('rescued rescue rescuing') }
+ it { should parse('rescued = assigned rescue rescuing') }
+ end
+
+ context "literals" do
+ it { should parse('1') }
+ it { should parse('1.1') }
+ it { should parse('nil') }
+ it { should parse('true') }
+ it { should parse('false') }
+ it { should parse('1..2') }
+ it { should parse('1...2') }
+ it { should parse('?a') }
+ end
+
+ context "methods" do
+ it { should parse("def foo(a, b = nil, c = :foo, *d, &block)\n bar\n baz\nend") }
+ it { should parse("def foo(a, b = {})\nend") }
+ it { should parse("def foo a, b = nil, c = :foo, *d, &block\n bar\n baz\nend") }
+ it { should parse("def self.for(options)\nend") }
+ it { should parse('def foo(a = 1, b=2); end') }
+ it { should parse('def t(a = []) end') }
+ it { should parse('def t(*) end') }
+ it { should parse('def <<(arg) end') }
+ it { should parse('def | ; end') }
+ it { should parse('class A < B; def |(foo); end; end') }
+ it { should parse('def <<(arg) foo; bar; end') }
+ it { should parse("def t\nrescue => e\nend") }
+ it { should parse("def a(b, c)\n d\nrescue A\n e\nensure\nb\nend") }
+ it { should parse('def foo ; bar { |k, v| k } end') }
+ it { should parse("class A\n def class\n end\nend") }
+ it { should parse("def end\nend") }
+ it { should parse('def def; 234; end') }
+ end
+
+ context "operators" do
+ context "unary" do
+ it { should parse('+1') }
+ it { should parse('-1') }
+ it { should parse('!1') }
+ it { should parse('not 1') }
+ it { should parse('not(1)') }
+ it { should parse('~1') }
+ it { should parse('(~1)') }
+ it { should parse('not (@main or @sub)') }
+ end
+
+ context "binary" do
+ context "mathematical" do
+ it { should parse('1 + 2') }
+ it { should parse('1 - 2') }
+ it { should parse('1 * 2') }
+ it { should parse('1 / 2') }
+ it { should parse('1 ** 2') }
+ it { should parse('1 % 2') }
+ it { should parse('(1 + 2)') }
+ end
+ context "logical" do
+ it { should parse('1 && 2') }
+ it { should parse('1 || 2') }
+ it { should parse('1 and 2') }
+ it { should parse('1 or 2') }
+ it { should parse('(1 and 2)') }
+ end
+ context "bitwise" do
+ it { should parse('1 << 2') }
+ it { should parse('1 >> 2') }
+ it { should parse('1 & 2') }
+ it { should parse('1 | 2') }
+ it { should parse('1 ^ 2') }
+ end
+ context "comparison, equality, matching" do
+ it { should parse('1 < 2') }
+ it { should parse('1 <= 2') }
+ it { should parse('1 > 2') }
+ it { should parse('1 >= 2') }
+ it { should parse('1 <=> 2') }
+ it { should parse('1 == 2') }
+ it { should parse('1 != 2') }
+ it { should parse('1 === 2') }
+ it { should parse('1 =~ 2') }
+ it { should parse('1 !~ 2') }
+ end
+ end
+
+ context "ternary" do
+ it { should parse('1 == 1 ? 2 : 3') }
+ it { should parse('((1) ? 2 : (3))') }
+ end
+ end
+
+ context "statements" do
+ it { should parse('foo') }
+ it { should parse(';foo') }
+ it { should parse('foo;') }
+ it { should parse(';foo;') }
+ it { should parse(';foo;;bar;baz;') }
+ it { should parse(';foo;;bar;;;;baz;') }
+ it { should parse(';;;foo;;bar;baz;;;;') }
+ it { should parse('(foo)') }
+ it { should parse('(((foo)); (bar))') }
+ it { should parse('(((foo)); ((bar); (baz)));') }
+ it { should parse("\n foo \n \n") }
+ it { should parse("foo\n__END__\nbar") }
+ end
+
+ context "string" do
+ it { should parse('""') }
+ it { should parse('"foo"') }
+ it { should parse("'foo'") }
+ it { should parse('%(foo)') }
+ it { should parse('%.foo.') }
+ it { should parse('%|foo|') }
+ it { should parse('"foo#{bar}"') }
+ it { should parse('%(foo #{bar})') }
+ it { should parse("%w(a)\n%(b)") }
+ it { should parse('/foo/') }
+ it { should parse('%r(foo)') }
+ it { should parse('"#{$1}"') }
+ it { should parse('"#$0"') }
+ it { should parse("'a' 'b'") }
+ it { should parse('`foo`') }
+ it { should parse('%x(foo)') }
+ end
+
+ context "symbol" do
+ it { should parse(':foo') }
+ it { should parse(':!') }
+ it { should parse(':-@') }
+ it { should parse(':if') }
+ it { should parse(':[]') }
+ it { should parse(':[]=') }
+ it { should parse(':"foo.bar"') }
+ it { should parse(":'foo.bar'") }
+ it { should parse(':"@#{token}"') }
+ end
+
+ context "unless" do
+ it { should parse('unless true; false end') }
+ it { should parse("unless true\n false end") }
+ it { should parse('unless true then false end') }
+ it { should parse('unless true; false; else; true end') }
+ it { should parse("unless true\n false\n else\n true end") }
+ it { should parse('foo unless true') }
+ it { should parse('1 unless false if true') }
+ end
+
+ context "until" do
+ it { should parse('until true; false end') }
+ it { should parse('until (true); false end') }
+ it { should parse('until (true;); false end') }
+ it { should parse("until true\n false end") }
+ it { should parse("until (true)\n false end") }
+ it { should parse('until foo do ; end') }
+ it { should parse('begin; false; end until true') }
+ it { should parse("begin\n false\n end until true") }
+ it { should parse('foo until true') }
+ it { should parse('foo until (true)') }
+ end
+end
405 spec/analyzer/policy_spec.rb
@@ -0,0 +1,405 @@
+require 'spec_helper'
+
+describe RubyCop::Policy do
+ let(:policy) { described_class.new }
+ subject { policy }
+
+ RSpec::Matchers.define(:allow) do |ruby|
+ match { |policy| RubyCop::NodeBuilder.build(ruby).accept(policy) }
+ end
+
+ context "assignment" do
+ context "class variables" do
+ it { should_not allow('@@x = 1') }
+ it { should_not allow('@@x ||= 1') }
+ it { should_not allow('@@x += 1') }
+ end
+
+ context "constants" do
+ it { should allow('Foo = 1') }
+ it { should allow('Foo::Bar = 1') }
+ it { should allow('::Bar = 1') }
+
+ it { should_not allow('Foo = Kernel') }
+ it { should_not allow('Foo = ::Kernel') }
+ it { should_not allow('Foo = Object::Kernel') }
+ end
+
+ context "globals" do
+ it { should_not allow('$x = 1') }
+ it { should_not allow('$x ||= 1') }
+ it { should_not allow('$x += 1') }
+ end
+
+ context "instance variables" do
+ it { should allow('@x = 1') }
+ it { should allow('@x += 1') }
+ it { should_not allow('@x = $x') }
+ it { should_not allow('@x = @@x') }
+ end
+
+ context "locals" do
+ it { should allow('x = 1') }
+ it { should allow('x ||= 1') }
+ it { should allow('x += 1') }
+ it { should_not allow('x = $x') }
+ it { should_not allow('x = @@x') }
+ end
+ end
+
+ context "begin/rescue/ensure" do
+ it { should allow('begin; x; rescue; end') }
+ it { should allow('x rescue 1') }
+
+ it { should_not allow('begin; `ls`; rescue; x; end') }
+ it { should_not allow('begin; x; rescue; `ls`; end') }
+ it { should_not allow('begin; x; rescue; 1; ensure `ls`; end') }
+ it { should_not allow('`ls` rescue 1') }
+ it { should_not allow('x rescue `ls`') }
+ it { should_not allow('begin; x; rescue (`ls`; RuntimeError) => err; end') }
+ end
+
+ context "blocks" do
+ it { should_not allow('->(a = $x) { }') }
+ it { should_not allow('->(a) { $x }') }
+ it { should_not allow('lambda { $x }') }
+ it { should_not allow('proc { $x }') }
+ end
+
+ context "calls" do
+ it { should allow('foo { 1 }') }
+ it { should_not allow('foo { $x }') }
+
+ context "blacklist" do
+ # This is a tricky case where we want to allow methods like
+ # Enumerable#select, but not Kernel#select / IO#select.
+ it { should allow('[1, 2, 3].select { |x| x.odd? }') }
+ it { pending('Kernel#select') { should_not allow('select([$stdin], nil, nil, 1.5)') } }
+
+ # TODO: these are a possible concern because symbols are not GC'ed and
+ # an attacker could create a large number of them to eat up memory. If
+ # these methods are blacklisted, then dyna-symbols (:"foo#{x}") need to
+ # be restricted as well.
+ it { should allow('"abc".intern') }
+ it { should allow('"abc".to_sym') }
+
+ it { should_not allow('abort("fail")') }
+ it { should_not allow('alias :foo :bar') }
+ it { should_not allow('alias foo bar') }
+ it { should_not allow('alias_method(:foo, :bar)') }
+ it { should_not allow('at_exit { puts "Bye!" }')}
+ it { should_not allow('autoload(:Foo, "foo")') }
+ it { should_not allow('binding') }
+ it { should_not allow('binding()') }
+ it { should_not allow('callcc { |cont| }') }
+ it { should_not allow('caller') }
+ it { should_not allow('caller()') }
+ it { should_not allow('caller(1)') }
+ it { should_not allow('class_eval("$x = 1")') }
+ it { should_not allow('const_get(:Kernel)') }
+ it { should_not allow('const_set(:Foo, ::Kernel)') }
+ it { should_not allow('eval("`ls`")') }
+ it { should_not allow('exec("ls")') }
+ it { should_not allow('exit') }
+ it { should_not allow('exit()') }
+ it { should_not allow('fail') }
+ it { should_not allow('fail("failed")') }
+ it { should_not allow('fail()') }
+ it { should_not allow('fork { }') }
+ it { should_not allow('fork') }
+ it { should_not allow('fork()') }
+ it { should_not allow('gets') }
+ it { should_not allow('gets()') }
+ it { should_not allow('global_variables') }
+ it { should_not allow('global_variables()') }
+ it { should_not allow('load("foo")') }
+ it { should_not allow('loop { }') }
+ it { should_not allow('method(:eval)') }
+ it { should_not allow('module_eval("`ls`")') }
+ it { should_not allow('open("/etc/passwd")') }
+ it { should_not allow('readline') }
+ it { should_not allow('readline()') }
+ it { should_not allow('readlines') }
+ it { should_not allow('readlines()') }
+ it { should_not allow('redo') }
+ it { should_not allow('remove_const(:Kernel)') }
+ it { should_not allow('require("digest/md5")') }
+ it { should_not allow('send(:eval, "`ls`")') }
+ it { should_not allow('set_trace_func(proc { |event,file,line,id,binding,classname| })') }
+ it { should_not allow('sleep(100**100)') }
+ it { should_not allow('spawn("ls", :chdir => "/")') }
+ it { should_not allow('srand') }
+ it { should_not allow('srand()') }
+ it { should_not allow('srand(1)') }
+ it { should_not allow('syscall(4, 1, "hello\n", 6)') }
+ it { should_not allow('system("ls")') }
+ it { should_not allow('trap("EXIT") { }') }
+ it { should_not allow('undef :raise') }
+ it { should_not allow('undef raise') }
+ end
+ end
+
+ context "case" do
+ it { should allow('case x; when 1; 2; end') }
+
+ it { should_not allow('case $x; when 1; 2; end') }
+ it { should_not allow('case $x = 1; when 1; 2; end') }
+ it { should_not allow('case x; when $x; 2; end') }
+ it { should_not allow('case x; when 1; $x; end') }
+ end
+
+ context "class / module definition" do
+ it { should allow("class Foo\nend") }
+ it { should allow("class Foo::Bar\nend") }
+
+ it { should allow("module Foo\nend") }
+ it { should allow("module Foo::Bar\nend") }
+ it { should_not allow("module Kernel\nend") }
+ it { should_not allow("module ::Kernel\nend") }
+ end
+
+ context "defined?" do
+ it { should_not allow('defined?(Kernel)') }
+ end
+
+ context "dynamic strings" do
+ it { should_not allow('"abc#{`ls`}"') }
+ it { should_not allow('"#{`ls`}abc"') }
+ it { should_not allow('"#$0"') }
+ end
+
+ context "dynamic symbols" do
+ it { should_not allow(':"abc#{`ls`}"') }
+ it { should_not allow(':"#{`ls`}abc"') }
+ end
+
+ context "for" do
+ it { should_not allow('for i in ENV; puts i; end') }
+ it { should_not allow('for $x in [1, 2, 3]; puts $x; end') }
+ end
+
+ context "if/elsif/else" do
+ it { should allow('x if true') }
+
+ it { should_not allow('$x ? 1 : 2') }
+ it { should_not allow('true ? $x : 2') }
+ it { should_not allow('true ? 1 : $x') }
+ it { should_not allow('if $x; 1; end') }
+ it { should_not allow('if true; $x; end') }
+ it { should_not allow('$x if true') }
+ it { should_not allow('true if $x') }
+ it { should_not allow('if $x; 1; else 2; end') }
+ it { should_not allow('if 1; $x; else 2; end') }
+ it { should_not allow('if 1; 1; else $x; end') }
+ it { should_not allow('if 1; 1; elsif 2; 2; else $x; end') }
+ end
+
+ context "literals" do
+ it { should allow('"abc"') }
+ it { should allow('/abc/') }
+ it { should allow('1') }
+ it { should allow('1..2') }
+ it { should allow('1.2') }
+ it { should allow('false') }
+ it { should allow('nil') }
+ it { should allow('true') }
+ it { should allow('[]') }
+ it { should allow('[1,2,3]') }
+ it { should allow('{}') }
+ it { should allow('{1 => 2}') }
+ end
+
+ context "magic variables" do
+ it { should_not allow('__callee__') }
+ it { should_not allow('__FILE__') }
+ it { should_not allow('__method__') }
+ end
+
+ context "methods" do
+ it { should allow('def initialize(attributes={}); end') }
+ end
+
+ context "singleton class" do
+ it { should_not allow('class << Kernel; end') }
+ it { should_not allow('class << Kernel; `ls`; end') }
+ end
+
+ context "super" do
+ it { should allow('super') }
+ it { should allow('super()') }
+ it { should allow('super(1)') }
+ it { should_not allow('super($x)') }
+ end
+
+ context "system" do
+ it { should_not allow('`ls`') }
+ it { should_not allow('%x[ls]') }
+ it { should_not allow('system("ls")') }
+ end
+
+ context "unless" do
+ it { should_not allow('unless $x; 1; end') }
+ it { should_not allow('unless true; $x; end') }
+ it { should_not allow('$x unless true') }
+ it { should_not allow('true unless $x') }
+ it { should_not allow('unless $x; 1; else 2; end') }
+ it { should_not allow('unless 1; $x; else 2; end') }
+ it { should_not allow('unless 1; 1; else $x; end') }
+ end
+
+ context "until" do
+ it { should_not allow('true until false') }
+ end
+
+ context "while" do
+ it { should_not allow('true while true') }
+ end
+
+ context "yield" do
+ it { should allow('def foo; yield; end') }
+ end
+
+ context "Rails for Zombies" do
+ before(:each) do
+ policy.whitelist_const('GenericController')
+ policy.whitelist_const('Tweet')
+ policy.whitelist_const('Weapon')
+ policy.whitelist_const('Zombie')
+ policy.whitelist_const('ZombiesController')
+ end
+
+ [
+ "1 = Ash\nAsh = Glen Haven Memorial Cemetary",
+ "<% zombies = Zombie.all %>\n\n<ul>\n <% zombies.each do |zombie| %>\n <li>\n <%= zombie.name %>\n <% if zombie.Tweet >= 1 %>\n <p><%= SMART ZOMBIE =%></p>\n <% end %>\n </li>\n <% end %>\n</ul>\n",
+ "class HelloRils",
+ "Class NAme\n\nend",
+ "class tweet < ActiveRecord::Base\n belongs_to :zombie \n z = zombie.find(2)\nend",
+ "class zombie < ActiveRecord :: Base\n\nend\n",
+ "Class Zombie < ActiveRecord::Base\n validates_presence_of :name\nend",
+ "Class Zombie < ActiveRecord::Base\nend",
+ "Class Zombie < ActiveRecord::Base\nvalidates_presence_of :status\nvalidates_presence_of :ww\nend",
+ "Class Zombie < ActiveRecord::Base{\ndef name\ndef graveyard\n\n}\n",
+ "class zombie < ActiveRecord\nend class",
+ "Class Zombie <ActiveRecord :: Base\n\nend\n\n\n",
+ "Class Zombie <ActiveRecord::Base>\nvalidates_presence_of\nend",
+ "class.load(Zombie)",
+ "Poop = Zombie.find(:id=1)",
+ "SELECT * WHERE ID = 1;",
+ "String myNewZombie = select name from Zombies where id=1",
+ "w = Weapon.find(1)\nZombie.create( :Weapon => \"Hammer\", Zombie => 1)\nend\n",
+ "Zodfsdsfdsdfsz=Zombies.find()1\n"
+ ].each do |error|
+ it "raises SyntaxError on #{error.inspect}" do
+ expect { RubyCop::NodeBuilder.build(error) }.to raise_error(SyntaxError)
+ end
+ end
+
+ [
+ "1\nZombie = 1\n",
+ "A = t.find(1)\n\n\n\n",
+ "Ash = 1\n",
+ "Ash = 1\n\n",
+ "Ash = Weapons.find.zombie_id(1)",
+ "Ash = Zombie.find(1)\nAsh.weapons.count",
+ "class Com\n\nhasmany dog\n\nend",
+ "class Finder < Tweet\n z = Tweet.find(1)\nend",
+ "class Post < ActiveRecord::Base\nend",
+ "class Weapons < ActiveRecord::Base\n belongs_to :Zombies\nend\n\nclass Zombies < ActiveRecord::Base\n has_many :Weapons\nend",
+ "Class Zombie < ActiveRecord::Base\n\nEnd",
+ "class Zombie < Rails::ActiveModel\n \nend",
+ "Class Zombie {\n validates :name, :presence => true\n}",
+ "Class Zombies < ActiveRecord::Base\nEnd",
+ "class ZombiesController < ApplicationController\n before_filter :find_zombie, :only => [:show]\n\n def show\n render :action => :show\n end\n\n def find_zombie\n @zombie = Zombie.find params[:id]\n @numTweets = Tweet.where(:zombie_id => @zombie).count\n if @numTweets < 1 \n redirect_to(zombies_path)\n end\n end\nend\n",
+ "class Zomvie <ActiveRecord::Base\nhas_many:Zombies\nend\n",
+ "class Zoombie < ActiveRecord::Base\nend\nz = Zoombie.last",
+ "class Zoombie\nend\nZoombie.create(:name => \"Jim\", :graveyard=> \"My Fathers Basement\")",
+ "cuntZombie=Zombies[1];",
+ "def creat