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)