lib/rubocop/ast_node.rb in rubocop-0.36.0 vs lib/rubocop/ast_node.rb in rubocop-0.37.0
- old
+ new
@@ -19,19 +19,20 @@
# lvar_node = node.each_descendant.find(&:lvar_type?)
#
class Node < Parser::AST::Node # rubocop:disable Metrics/ClassLength
include RuboCop::Sexp
- COMPARISON_OPERATORS = [:==, :===, :!=, :<=, :>=, :>, :<, :<=>].freeze
+ COMPARISON_OPERATORS = [:!, :==, :===, :!=, :<=, :>=, :>, :<, :<=>].freeze
TRUTHY_LITERALS = [:str, :dstr, :xstr, :int, :float, :sym, :dsym, :array,
:hash, :regexp, :true, :irange, :erange, :complex,
- :rational].freeze
+ :rational, :regopt].freeze
FALSEY_LITERALS = [:false, :nil].freeze
LITERALS = (TRUTHY_LITERALS + FALSEY_LITERALS).freeze
- BASIC_LITERALS = LITERALS - [:dstr, :xstr, :dsym, :array, :hash, :irange,
- :erange].freeze
+ COMPOSITE_LITERALS = [:dstr, :xstr, :dsym, :array, :hash, :irange,
+ :erange, :regexp].freeze
+ BASIC_LITERALS = (LITERALS - COMPOSITE_LITERALS).freeze
MUTABLE_LITERALS = [:str, :dstr, :xstr, :array, :hash].freeze
IMMUTABLE_LITERALS = (LITERALS - MUTABLE_LITERALS).freeze
VARIABLES = [:ivar, :gvar, :cvar, :lvar].freeze
REFERENCES = [:nth_ref, :back_ref].freeze
@@ -40,10 +41,12 @@
:or, :postexe, :redo, :rescue, :retry, :return, :self, :super,
:zsuper, :then, :undef, :until, :when, :while, :yield].freeze
OPERATOR_KEYWORDS = [:and, :or].freeze
SPECIAL_KEYWORDS = %w(__FILE__ __LINE__ __ENCODING__).freeze
+ RSPEC_METHODS = [:describe, :it].freeze
+
# def_matcher can be used to define a pattern-matching method on Node
class << self
def def_matcher(method_name, pattern_str)
compiler = RuboCop::NodePattern::Compiler.new(pattern_str, 'self')
src = "def #{method_name}(" \
@@ -66,11 +69,11 @@
# pending nodes while constructing AST and they are replaced later.
# For example, `lvar` and `send` type nodes are initially created as an
# `ident` type node and fixed to the appropriate type later.
# So, the #parent attribute needs to be mutable.
each_child_node do |child_node|
- child_node.parent = self
+ child_node.parent = self unless child_node.complete?
end
end
Parser::Meta::NODE_TYPES.each do |node_type|
method_name = "#{node_type.to_s.gsub(/\W/, '')}_type?"
@@ -88,12 +91,32 @@
def parent=(node)
@mutable_attributes[:parent] = node
end
+ def complete!
+ @mutable_attributes.freeze
+ each_child_node(&:complete!)
+ end
+
+ def complete?
+ @mutable_attributes.frozen?
+ end
+
protected :parent=
+ # Override `AST::Node#updated` so that `AST::Processor` does not try to
+ # mutate our ASTs. Since we keep references from children to parents and
+ # not just the other way around, we cannot update an AST and share identical
+ # subtrees. Rather, the entire AST must be copied any time any part of it
+ # is changed.
+ #
+ def updated(type = nil, children = nil, properties = {})
+ properties[:location] ||= @location
+ Node.new(type || @type, children || @children, properties)
+ end
+
# Returns the index of the receiver node in its siblings.
#
# @return [Integer] the index of the receiver node in its siblings
def sibling_index
parent.children.index { |sibling| sibling.equal?(self) }
@@ -118,12 +141,10 @@
# @return [self] if a block is given
# @return [Enumerator] if no block is given
def each_ancestor(*types, &block)
return to_enum(__method__, *types) unless block_given?
- types.flatten!
-
if types.empty?
visit_ancestors(&block)
else
visit_ancestors_with_types(types, &block)
end
@@ -161,12 +182,10 @@
# @return [self] if a block is given
# @return [Enumerator] if no block is given
def each_child_node(*types)
return to_enum(__method__, *types) unless block_given?
- types.flatten!
-
children.each do |child|
next unless child.is_a?(Node)
yield child if types.empty? || types.include?(child.type)
end
@@ -200,12 +219,10 @@
# @return [self] if a block is given
# @return [Enumerator] if no block is given
def each_descendant(*types, &block)
return to_enum(__method__, *types) unless block_given?
- types.flatten!
-
if types.empty?
visit_descendants(&block)
else
visit_descendants_with_types(types, &block)
end
@@ -244,12 +261,10 @@
# @return [self] if a block is given
# @return [Enumerator] if no block is given
def each_node(*types, &block)
return to_enum(__method__, *types) unless block_given?
- types.flatten!
-
yield self if types.empty? || types.include?(type)
if types.empty?
visit_descendants(&block)
else
@@ -321,14 +336,12 @@
# inside a class or module
return "#<Class:#{obj.const_name}>" if obj.const_type?
return "#<Class:#{ancestor.parent_module_name}>" if obj.self_type?
return nil
else # block
- # Known DSL methods which eval body inside an anonymous class/module
- return nil if [:describe, :it].include?(ancestor.method_name) &&
- ancestor.receiver.nil?
- if ancestor.method_name == :class_eval
+ return nil if ancestor.known_dsl?
+ if ancestor.method_name == :class_eval && ancestor.receiver
return nil unless ancestor.receiver.const_type?
ancestor.receiver.const_name
end
end
end.compact.reverse.join('::')
@@ -346,11 +359,11 @@
!multiline?
end
def asgn_method_call?
!COMPARISON_OPERATORS.include?(method_name) &&
- method_name.to_s.end_with?('=')
+ method_name.to_s.end_with?('='.freeze)
end
def_matcher :equals_asgn?, '{lvasgn ivasgn cvasgn gvasgn casgn masgn}'
def_matcher :shorthand_asgn?, '{op_asgn or_asgn and_asgn}'
def_matcher :assignment?, '{equals_asgn? shorthand_asgn? asgn_method_call?}'
@@ -377,10 +390,28 @@
def immutable_literal?
IMMUTABLE_LITERALS.include?(type)
end
+ [:literal, :basic_literal].each do |kind|
+ recursive_kind = :"recursive_#{kind}?"
+ kind_filter = :"#{kind}?"
+ define_method(recursive_kind) do
+ case type
+ when :begin, :pair, *OPERATOR_KEYWORDS, *COMPOSITE_LITERALS
+ children.all?(&recursive_kind)
+ when :send
+ receiver, method_name, *args = *self
+ COMPARISON_OPERATORS.include?(method_name) &&
+ receiver.send(recursive_kind) &&
+ args.all?(&recursive_kind)
+ else
+ send(kind_filter)
+ end
+ end
+ end
+
def variable?
VARIABLES.include?(type)
end
def reference?
@@ -401,10 +432,22 @@
def keyword_not?
_receiver, method_name, *args = *self
args.empty? && method_name == :! && loc.selector.is?('not'.freeze)
end
+ def unary_operation?
+ return false unless loc.respond_to?(:selector) && loc.selector
+ Cop::Util.operator?(loc.selector.source.to_sym) &&
+ source_range.begin_pos == loc.selector.begin_pos
+ end
+
+ def chained?
+ return false if parent.nil? || !parent.send_type?
+ receiver, _method_name, *_args = *parent
+ equal?(receiver)
+ end
+
def_matcher :command?, '(send nil %1 ...)'
def_matcher :lambda?, '(block (send nil :lambda) ...)'
def_matcher :proc?, <<-PATTERN
{(block (send nil :proc) ...)
(block (send (const nil :Proc) :new) ...)
@@ -468,18 +511,23 @@
#
def pure?
# Be conservative and return false if we're not sure
case type
when :__FILE__, :__LINE__, :const, :cvar, :defined?, :false, :float,
- :gvar, :int, :ivar, :lvar, :nil, :str, :sym, :true
+ :gvar, :int, :ivar, :lvar, :nil, :str, :sym, :true, :regopt
true
when :and, :array, :begin, :case, :dstr, :dsym, :eflipflop, :ensure,
:erange, :for, :hash, :if, :iflipflop, :irange, :kwbegin, :not, :or,
:pair, :regexp, :until, :until_post, :when, :while, :while_post
child_nodes.all?(&:pure?)
else
false
end
+ end
+
+ # Known DSL methods which eval body inside an anonymous class/module
+ def known_dsl?
+ RSPEC_METHODS.include?(method_name) && receiver.nil?
end
protected
def visit_descendants(&block)